Before going crazy
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,3 +2,7 @@
|
|||||||
Cargo.lock
|
Cargo.lock
|
||||||
*.prof
|
*.prof
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Claude
|
||||||
|
.claude/
|
||||||
|
CLAUDE.md
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,15 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::project::{Bank, Project};
|
use crate::project::{Bank, Project};
|
||||||
|
|
||||||
const VERSION: u8 = 1;
|
const VERSION: u8 = 1;
|
||||||
|
pub const EXTENSION: &str = "cagire";
|
||||||
|
|
||||||
|
pub fn ensure_extension(path: &Path) -> PathBuf {
|
||||||
|
if path.extension().map(|e| e == EXTENSION).unwrap_or(false) {
|
||||||
|
path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
path.with_extension(EXTENSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct ProjectFile {
|
struct ProjectFile {
|
||||||
@@ -74,11 +83,12 @@ impl From<serde_json::Error> for FileError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(project: &Project, path: &Path) -> Result<(), FileError> {
|
pub fn save(project: &Project, path: &Path) -> Result<PathBuf, FileError> {
|
||||||
|
let path = ensure_extension(path);
|
||||||
let file = ProjectFile::from(project);
|
let file = ProjectFile::from(project);
|
||||||
let json = serde_json::to_string_pretty(&file)?;
|
let json = serde_json::to_string_pretty(&file)?;
|
||||||
fs::write(path, json)?;
|
fs::write(&path, json)?;
|
||||||
Ok(())
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(path: &Path) -> Result<Project, FileError> {
|
pub fn load(path: &Path) -> Result<Project, FileError> {
|
||||||
|
|||||||
@@ -216,6 +216,12 @@ pub struct Step {
|
|||||||
pub source: Option<usize>,
|
pub source: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Step {
|
||||||
|
pub fn is_default(&self) -> bool {
|
||||||
|
self.active && self.script.is_empty() && self.source.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Step {
|
impl Default for Step {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -227,20 +233,141 @@ impl Default for Step {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone)]
|
||||||
pub struct Pattern {
|
pub struct Pattern {
|
||||||
pub steps: Vec<Step>,
|
pub steps: Vec<Step>,
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
#[serde(default)]
|
|
||||||
pub speed: PatternSpeed,
|
pub speed: PatternSpeed,
|
||||||
#[serde(default)]
|
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default)]
|
|
||||||
pub quantization: LaunchQuantization,
|
pub quantization: LaunchQuantization,
|
||||||
#[serde(default)]
|
|
||||||
pub sync_mode: SyncMode,
|
pub sync_mode: SyncMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct SparseStep {
|
||||||
|
i: usize,
|
||||||
|
#[serde(default = "default_active", skip_serializing_if = "is_true")]
|
||||||
|
active: bool,
|
||||||
|
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||||
|
script: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
source: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_active() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_true(v: &bool) -> bool {
|
||||||
|
*v
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct SparsePattern {
|
||||||
|
steps: Vec<SparseStep>,
|
||||||
|
length: usize,
|
||||||
|
#[serde(default)]
|
||||||
|
speed: PatternSpeed,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
name: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "is_default_quantization")]
|
||||||
|
quantization: LaunchQuantization,
|
||||||
|
#[serde(default, skip_serializing_if = "is_default_sync_mode")]
|
||||||
|
sync_mode: SyncMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_default_quantization(q: &LaunchQuantization) -> bool {
|
||||||
|
*q == LaunchQuantization::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_default_sync_mode(s: &SyncMode) -> bool {
|
||||||
|
*s == SyncMode::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct LegacyPattern {
|
||||||
|
steps: Vec<Step>,
|
||||||
|
length: usize,
|
||||||
|
#[serde(default)]
|
||||||
|
speed: PatternSpeed,
|
||||||
|
#[serde(default)]
|
||||||
|
name: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
quantization: LaunchQuantization,
|
||||||
|
#[serde(default)]
|
||||||
|
sync_mode: SyncMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Pattern {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
let sparse_steps: Vec<SparseStep> = self
|
||||||
|
.steps
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, step)| !step.is_default())
|
||||||
|
.map(|(i, step)| SparseStep {
|
||||||
|
i,
|
||||||
|
active: step.active,
|
||||||
|
script: step.script.clone(),
|
||||||
|
source: step.source,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let sparse = SparsePattern {
|
||||||
|
steps: sparse_steps,
|
||||||
|
length: self.length,
|
||||||
|
speed: self.speed,
|
||||||
|
name: self.name.clone(),
|
||||||
|
quantization: self.quantization,
|
||||||
|
sync_mode: self.sync_mode,
|
||||||
|
};
|
||||||
|
sparse.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Pattern {
|
||||||
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum PatternFormat {
|
||||||
|
Sparse(SparsePattern),
|
||||||
|
Legacy(LegacyPattern),
|
||||||
|
}
|
||||||
|
|
||||||
|
match PatternFormat::deserialize(deserializer)? {
|
||||||
|
PatternFormat::Sparse(sparse) => {
|
||||||
|
let mut steps: Vec<Step> = (0..MAX_STEPS).map(|_| Step::default()).collect();
|
||||||
|
for ss in sparse.steps {
|
||||||
|
if ss.i < MAX_STEPS {
|
||||||
|
steps[ss.i] = Step {
|
||||||
|
active: ss.active,
|
||||||
|
script: ss.script,
|
||||||
|
command: None,
|
||||||
|
source: ss.source,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Pattern {
|
||||||
|
steps,
|
||||||
|
length: sparse.length,
|
||||||
|
speed: sparse.speed,
|
||||||
|
name: sparse.name,
|
||||||
|
quantization: sparse.quantization,
|
||||||
|
sync_mode: sparse.sync_mode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
PatternFormat::Legacy(legacy) => Ok(Pattern {
|
||||||
|
steps: legacy.steps,
|
||||||
|
length: legacy.length,
|
||||||
|
speed: legacy.speed,
|
||||||
|
name: legacy.name,
|
||||||
|
quantization: legacy.quantization,
|
||||||
|
sync_mode: legacy.sync_mode,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Pattern {
|
impl Default for Pattern {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use super::ModalFrame;
|
|||||||
pub struct FileBrowserModal<'a> {
|
pub struct FileBrowserModal<'a> {
|
||||||
title: &'a str,
|
title: &'a str,
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
entries: &'a [(String, bool)],
|
entries: &'a [(String, bool, bool)],
|
||||||
selected: usize,
|
selected: usize,
|
||||||
scroll_offset: usize,
|
scroll_offset: usize,
|
||||||
border_color: Color,
|
border_color: Color,
|
||||||
@@ -18,7 +18,7 @@ pub struct FileBrowserModal<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FileBrowserModal<'a> {
|
impl<'a> FileBrowserModal<'a> {
|
||||||
pub fn new(title: &'a str, input: &'a str, entries: &'a [(String, bool)]) -> Self {
|
pub fn new(title: &'a str, input: &'a str, entries: &'a [(String, bool, bool)]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
title,
|
title,
|
||||||
input,
|
input,
|
||||||
@@ -85,7 +85,7 @@ impl<'a> FileBrowserModal<'a> {
|
|||||||
|
|
||||||
let lines: Vec<Line> = visible_entries
|
let lines: Vec<Line> = visible_entries
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, (name, is_dir))| {
|
.map(|(i, (name, is_dir, is_cagire))| {
|
||||||
let abs_idx = i + self.scroll_offset;
|
let abs_idx = i + self.scroll_offset;
|
||||||
let is_selected = abs_idx == self.selected;
|
let is_selected = abs_idx == self.selected;
|
||||||
let prefix = if is_selected { "> " } else { " " };
|
let prefix = if is_selected { "> " } else { " " };
|
||||||
@@ -98,6 +98,8 @@ impl<'a> FileBrowserModal<'a> {
|
|||||||
Color::Yellow
|
Color::Yellow
|
||||||
} else if *is_dir {
|
} else if *is_dir {
|
||||||
Color::Blue
|
Color::Blue
|
||||||
|
} else if *is_cagire {
|
||||||
|
Color::Magenta
|
||||||
} else {
|
} else {
|
||||||
Color::White
|
Color::White
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -495,9 +495,9 @@ impl App {
|
|||||||
self.project_state.project.sample_paths = self.audio.config.sample_paths.clone();
|
self.project_state.project.sample_paths = self.audio.config.sample_paths.clone();
|
||||||
self.project_state.project.tempo = link.tempo();
|
self.project_state.project.tempo = link.tempo();
|
||||||
match model::save(&self.project_state.project, &path) {
|
match model::save(&self.project_state.project, &path) {
|
||||||
Ok(()) => {
|
Ok(final_path) => {
|
||||||
self.ui.set_status(format!("Saved: {}", path.display()));
|
self.ui.set_status(format!("Saved: {}", final_path.display()));
|
||||||
self.project_state.file_path = Some(path);
|
self.project_state.file_path = Some(final_path);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.ui.set_status(format!("Save error: {e}"));
|
self.ui.set_status(format!("Save error: {e}"));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
mod script;
|
mod script;
|
||||||
|
|
||||||
pub use cagire_forth::{Word, WORDS};
|
pub use cagire_forth::{Word, WordCompile, WORDS};
|
||||||
pub use cagire_project::{
|
pub use cagire_project::{
|
||||||
load, save, Bank, LaunchQuantization, Pattern, PatternSpeed, Project, SyncMode, MAX_BANKS,
|
load, save, Bank, LaunchQuantization, Pattern, PatternSpeed, Project, SyncMode, MAX_BANKS,
|
||||||
MAX_PATTERNS,
|
MAX_PATTERNS,
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ pub struct DirEntry {
|
|||||||
pub is_dir: bool,
|
pub is_dir: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DirEntry {
|
||||||
|
pub fn is_cagire(&self) -> bool {
|
||||||
|
!self.is_dir && self.name.ends_with(".cagire")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct FileBrowserState {
|
pub struct FileBrowserState {
|
||||||
pub mode: FileBrowserMode,
|
pub mode: FileBrowserMode,
|
||||||
|
|||||||
@@ -109,7 +109,10 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
|||||||
let query = app.ui.dict_search_query.to_lowercase();
|
let query = app.ui.dict_search_query.to_lowercase();
|
||||||
WORDS
|
WORDS
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|w| w.name.to_lowercase().contains(&query))
|
.filter(|w| {
|
||||||
|
w.name.to_lowercase().contains(&query)
|
||||||
|
|| w.aliases.iter().any(|a| a.to_lowercase().contains(&query))
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
let category = CATEGORIES[app.ui.dict_category];
|
let category = CATEGORIES[app.ui.dict_category];
|
||||||
@@ -144,12 +147,25 @@ fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
|
|||||||
.fg(Color::Green)
|
.fg(Color::Green)
|
||||||
.bg(name_bg)
|
.bg(name_bg)
|
||||||
.add_modifier(Modifier::BOLD);
|
.add_modifier(Modifier::BOLD);
|
||||||
let name_line = format!(" {}", word.name);
|
let alias_style = Style::new().fg(Color::DarkGray).bg(name_bg);
|
||||||
let padding = " ".repeat(content_width.saturating_sub(name_line.chars().count()));
|
let name_text = if word.aliases.is_empty() {
|
||||||
lines.push(RLine::from(Span::styled(
|
format!(" {}", word.name)
|
||||||
format!("{name_line}{padding}"),
|
} else {
|
||||||
name_style,
|
format!(" {} ({})", word.name, word.aliases.join(", "))
|
||||||
)));
|
};
|
||||||
|
let padding = " ".repeat(content_width.saturating_sub(name_text.chars().count()));
|
||||||
|
if word.aliases.is_empty() {
|
||||||
|
lines.push(RLine::from(Span::styled(
|
||||||
|
format!("{name_text}{padding}"),
|
||||||
|
name_style,
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
lines.push(RLine::from(vec![
|
||||||
|
Span::styled(format!(" {}", word.name), name_style),
|
||||||
|
Span::styled(format!(" ({})", word.aliases.join(", ")), alias_style),
|
||||||
|
Span::styled(padding, name_style),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
let stack_style = Style::new().fg(Color::Magenta);
|
let stack_style = Style::new().fg(Color::Magenta);
|
||||||
lines.push(RLine::from(vec![
|
lines.push(RLine::from(vec![
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
|
||||||
use crate::model::SourceSpan;
|
use crate::model::{SourceSpan, WordCompile, WORDS};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum TokenKind {
|
pub enum TokenKind {
|
||||||
@@ -45,223 +45,49 @@ pub struct Token {
|
|||||||
pub kind: TokenKind,
|
pub kind: TokenKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
const STACK_OPS: &[&str] = &["dup", "dupn", "drop", "swap", "over", "rot", "nip", "tuck"];
|
fn lookup_word_kind(word: &str) -> Option<TokenKind> {
|
||||||
const OPERATORS: &[&str] = &[
|
for w in WORDS {
|
||||||
"+", "-", "*", "/", "mod", "neg", "abs", "min", "max", "=", "<>", "<", ">", "<=", ">=", "and",
|
if w.name == word || w.aliases.contains(&word) {
|
||||||
"or", "not", "ceil", "floor", "round", "mtof", "ftom",
|
return Some(match &w.compile {
|
||||||
];
|
WordCompile::Param => TokenKind::Param,
|
||||||
const KEYWORDS: &[&str] = &[
|
WordCompile::Context(_) => TokenKind::Context,
|
||||||
"if",
|
_ => match w.category {
|
||||||
"else",
|
"Stack" => TokenKind::StackOp,
|
||||||
"then",
|
"Arithmetic" | "Comparison" | "Music" => TokenKind::Operator,
|
||||||
"emit",
|
"Logic" if matches!(w.name, "and" | "or" | "not" | "xor" | "nand" | "nor") => {
|
||||||
"rand",
|
TokenKind::Operator
|
||||||
"rrand",
|
}
|
||||||
"seed",
|
"Sound" => TokenKind::Sound,
|
||||||
"cycle",
|
_ => TokenKind::Keyword,
|
||||||
"choose",
|
},
|
||||||
"chance",
|
});
|
||||||
"[",
|
}
|
||||||
"]",
|
}
|
||||||
"zoom",
|
None
|
||||||
"scale!",
|
}
|
||||||
"stack",
|
|
||||||
"echo",
|
fn is_note(word: &str) -> bool {
|
||||||
"necho",
|
let bytes = word.as_bytes();
|
||||||
"for",
|
if bytes.len() < 2 {
|
||||||
"div",
|
return false;
|
||||||
"each",
|
}
|
||||||
"at",
|
let base = matches!(bytes[0], b'c' | b'd' | b'e' | b'f' | b'g' | b'a' | b'b');
|
||||||
"pop",
|
if !base {
|
||||||
"adsr",
|
return false;
|
||||||
"ad",
|
}
|
||||||
"?",
|
match bytes[1] {
|
||||||
"!?",
|
b'#' | b's' | b'b' => bytes.len() > 2 && bytes[2..].iter().all(|b| b.is_ascii_digit()),
|
||||||
"<<",
|
b'0'..=b'9' => bytes[1..].iter().all(|b| b.is_ascii_digit()),
|
||||||
">>",
|
_ => false,
|
||||||
"|",
|
}
|
||||||
"@",
|
}
|
||||||
"!",
|
|
||||||
"pcycle",
|
|
||||||
"tempo!",
|
|
||||||
"prob",
|
|
||||||
"sometimes",
|
|
||||||
"often",
|
|
||||||
"rarely",
|
|
||||||
"almostAlways",
|
|
||||||
"almostNever",
|
|
||||||
"always",
|
|
||||||
"never",
|
|
||||||
"coin",
|
|
||||||
"fill",
|
|
||||||
"iter",
|
|
||||||
"every",
|
|
||||||
"gt",
|
|
||||||
"lt",
|
|
||||||
":",
|
|
||||||
";",
|
|
||||||
"apply",
|
|
||||||
"clear",
|
|
||||||
];
|
|
||||||
const SOUND: &[&str] = &["sound", "s"];
|
|
||||||
const CONTEXT: &[&str] = &[
|
|
||||||
"step", "beat", "bank", "pattern", "tempo", "phase", "slot", "runs", "stepdur",
|
|
||||||
];
|
|
||||||
const PARAMS: &[&str] = &[
|
|
||||||
"time",
|
|
||||||
"repeat",
|
|
||||||
"dur",
|
|
||||||
"gate",
|
|
||||||
"freq",
|
|
||||||
"detune",
|
|
||||||
"speed",
|
|
||||||
"glide",
|
|
||||||
"pw",
|
|
||||||
"spread",
|
|
||||||
"mult",
|
|
||||||
"warp",
|
|
||||||
"mirror",
|
|
||||||
"harmonics",
|
|
||||||
"timbre",
|
|
||||||
"morph",
|
|
||||||
"begin",
|
|
||||||
"end",
|
|
||||||
"gain",
|
|
||||||
"postgain",
|
|
||||||
"velocity",
|
|
||||||
"pan",
|
|
||||||
"attack",
|
|
||||||
"decay",
|
|
||||||
"sustain",
|
|
||||||
"release",
|
|
||||||
"lpf",
|
|
||||||
"lpq",
|
|
||||||
"lpe",
|
|
||||||
"lpa",
|
|
||||||
"lpd",
|
|
||||||
"lps",
|
|
||||||
"lpr",
|
|
||||||
"hpf",
|
|
||||||
"hpq",
|
|
||||||
"hpe",
|
|
||||||
"hpa",
|
|
||||||
"hpd",
|
|
||||||
"hps",
|
|
||||||
"hpr",
|
|
||||||
"bpf",
|
|
||||||
"bpq",
|
|
||||||
"bpe",
|
|
||||||
"bpa",
|
|
||||||
"bpd",
|
|
||||||
"bps",
|
|
||||||
"bpr",
|
|
||||||
"ftype",
|
|
||||||
"penv",
|
|
||||||
"patt",
|
|
||||||
"pdec",
|
|
||||||
"psus",
|
|
||||||
"prel",
|
|
||||||
"vib",
|
|
||||||
"vibmod",
|
|
||||||
"vibshape",
|
|
||||||
"fm",
|
|
||||||
"fmh",
|
|
||||||
"fmshape",
|
|
||||||
"fme",
|
|
||||||
"fma",
|
|
||||||
"fmd",
|
|
||||||
"fms",
|
|
||||||
"fmr",
|
|
||||||
"am",
|
|
||||||
"amdepth",
|
|
||||||
"amshape",
|
|
||||||
"rm",
|
|
||||||
"rmdepth",
|
|
||||||
"rmshape",
|
|
||||||
"phaser",
|
|
||||||
"phaserdepth",
|
|
||||||
"phasersweep",
|
|
||||||
"phasercenter",
|
|
||||||
"flanger",
|
|
||||||
"flangerdepth",
|
|
||||||
"flangerfeedback",
|
|
||||||
"chorus",
|
|
||||||
"chorusdepth",
|
|
||||||
"chorusdelay",
|
|
||||||
"comb",
|
|
||||||
"combfreq",
|
|
||||||
"combfeedback",
|
|
||||||
"combdamp",
|
|
||||||
"coarse",
|
|
||||||
"crush",
|
|
||||||
"fold",
|
|
||||||
"wrap",
|
|
||||||
"distort",
|
|
||||||
"distortvol",
|
|
||||||
"delay",
|
|
||||||
"delaytime",
|
|
||||||
"delayfeedback",
|
|
||||||
"delaytype",
|
|
||||||
"verb",
|
|
||||||
"verbdecay",
|
|
||||||
"verbdamp",
|
|
||||||
"verbpredelay",
|
|
||||||
"verbdiff",
|
|
||||||
"voice",
|
|
||||||
"orbit",
|
|
||||||
"note",
|
|
||||||
"size",
|
|
||||||
"n",
|
|
||||||
"cut",
|
|
||||||
"reset",
|
|
||||||
"eqlo",
|
|
||||||
"eqmid",
|
|
||||||
"eqhi",
|
|
||||||
"tilt",
|
|
||||||
"width",
|
|
||||||
"haas",
|
|
||||||
"llpf",
|
|
||||||
"llpq",
|
|
||||||
"lhpf",
|
|
||||||
"lhpq",
|
|
||||||
"lbpf",
|
|
||||||
"lbpq",
|
|
||||||
"sub",
|
|
||||||
"suboct",
|
|
||||||
"subwave",
|
|
||||||
"bank",
|
|
||||||
"loop",
|
|
||||||
];
|
|
||||||
const INTERVALS: &[&str] = &[
|
const INTERVALS: &[&str] = &[
|
||||||
"P1", "unison", "m2", "M2", "m3", "M3", "P4", "aug4", "dim5", "tritone", "P5", "m6", "M6",
|
"P1", "unison", "m2", "M2", "m3", "M3", "P4", "aug4", "dim5", "tritone", "P5", "m6", "M6",
|
||||||
"m7", "M7", "P8", "oct", "m9", "M9", "m10", "M10", "P11", "aug11", "P12", "m13", "M13", "m14",
|
"m7", "M7", "P8", "oct", "m9", "M9", "m10", "M10", "P11", "aug11", "P12", "m13", "M13", "m14",
|
||||||
"M14", "P15",
|
"M14", "P15",
|
||||||
];
|
];
|
||||||
|
|
||||||
const NOTES: &[&str] = &[
|
|
||||||
"c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "d0", "d1", "d2", "d3", "d4", "d5",
|
|
||||||
"d6", "d7", "d8", "d9", "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "f0", "f1",
|
|
||||||
"f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7",
|
|
||||||
"g8", "g9", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "b0", "b1", "b2", "b3",
|
|
||||||
"b4", "b5", "b6", "b7", "b8", "b9", "cs0", "cs1", "cs2", "cs3", "cs4", "cs5", "cs6", "cs7",
|
|
||||||
"cs8", "cs9", "ds0", "ds1", "ds2", "ds3", "ds4", "ds5", "ds6", "ds7", "ds8", "ds9", "es0",
|
|
||||||
"es1", "es2", "es3", "es4", "es5", "es6", "es7", "es8", "es9", "fs0", "fs1", "fs2", "fs3",
|
|
||||||
"fs4", "fs5", "fs6", "fs7", "fs8", "fs9", "gs0", "gs1", "gs2", "gs3", "gs4", "gs5", "gs6",
|
|
||||||
"gs7", "gs8", "gs9", "as0", "as1", "as2", "as3", "as4", "as5", "as6", "as7", "as8", "as9",
|
|
||||||
"bs0", "bs1", "bs2", "bs3", "bs4", "bs5", "bs6", "bs7", "bs8", "bs9", "cb0", "cb1", "cb2",
|
|
||||||
"cb3", "cb4", "cb5", "cb6", "cb7", "cb8", "cb9", "db0", "db1", "db2", "db3", "db4", "db5",
|
|
||||||
"db6", "db7", "db8", "db9", "eb0", "eb1", "eb2", "eb3", "eb4", "eb5", "eb6", "eb7", "eb8",
|
|
||||||
"eb9", "fb0", "fb1", "fb2", "fb3", "fb4", "fb5", "fb6", "fb7", "fb8", "fb9", "gb0", "gb1",
|
|
||||||
"gb2", "gb3", "gb4", "gb5", "gb6", "gb7", "gb8", "gb9", "ab0", "ab1", "ab2", "ab3", "ab4",
|
|
||||||
"ab5", "ab6", "ab7", "ab8", "ab9", "bb0", "bb1", "bb2", "bb3", "bb4", "bb5", "bb6", "bb7",
|
|
||||||
"bb8", "bb9", "c#0", "c#1", "c#2", "c#3", "c#4", "c#5", "c#6", "c#7", "c#8", "c#9", "d#0",
|
|
||||||
"d#1", "d#2", "d#3", "d#4", "d#5", "d#6", "d#7", "d#8", "d#9", "e#0", "e#1", "e#2", "e#3",
|
|
||||||
"e#4", "e#5", "e#6", "e#7", "e#8", "e#9", "f#0", "f#1", "f#2", "f#3", "f#4", "f#5", "f#6",
|
|
||||||
"f#7", "f#8", "f#9", "g#0", "g#1", "g#2", "g#3", "g#4", "g#5", "g#6", "g#7", "g#8", "g#9",
|
|
||||||
"a#0", "a#1", "a#2", "a#3", "a#4", "a#5", "a#6", "a#7", "a#8", "a#9", "b#0", "b#1", "b#2",
|
|
||||||
"b#3", "b#4", "b#5", "b#6", "b#7", "b#8", "b#9",
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn tokenize_line(line: &str) -> Vec<Token> {
|
pub fn tokenize_line(line: &str) -> Vec<Token> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
let mut chars = line.char_indices().peekable();
|
let mut chars = line.char_indices().peekable();
|
||||||
@@ -329,36 +155,15 @@ fn classify_word(word: &str) -> TokenKind {
|
|||||||
return TokenKind::Number;
|
return TokenKind::Number;
|
||||||
}
|
}
|
||||||
|
|
||||||
if STACK_OPS.contains(&word) {
|
if let Some(kind) = lookup_word_kind(word) {
|
||||||
return TokenKind::StackOp;
|
return kind;
|
||||||
}
|
|
||||||
|
|
||||||
if OPERATORS.contains(&word) {
|
|
||||||
return TokenKind::Operator;
|
|
||||||
}
|
|
||||||
|
|
||||||
if KEYWORDS.contains(&word) {
|
|
||||||
return TokenKind::Keyword;
|
|
||||||
}
|
|
||||||
|
|
||||||
if SOUND.contains(&word) {
|
|
||||||
return TokenKind::Sound;
|
|
||||||
}
|
|
||||||
|
|
||||||
if CONTEXT.contains(&word) {
|
|
||||||
return TokenKind::Context;
|
|
||||||
}
|
|
||||||
|
|
||||||
if PARAMS.contains(&word) {
|
|
||||||
return TokenKind::Param;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if INTERVALS.contains(&word) {
|
if INTERVALS.contains(&word) {
|
||||||
return TokenKind::Interval;
|
return TokenKind::Interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lower = word.to_ascii_lowercase();
|
if is_note(&word.to_ascii_lowercase()) {
|
||||||
if NOTES.contains(&lower.as_str()) {
|
|
||||||
return TokenKind::Note;
|
return TokenKind::Note;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -431,10 +431,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
|||||||
FileBrowserMode::Save => ("Save As", Color::Green),
|
FileBrowserMode::Save => ("Save As", Color::Green),
|
||||||
FileBrowserMode::Load => ("Load From", Color::Blue),
|
FileBrowserMode::Load => ("Load From", Color::Blue),
|
||||||
};
|
};
|
||||||
let entries: Vec<(String, bool)> = state
|
let entries: Vec<(String, bool, bool)> = state
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| (e.name.clone(), e.is_dir))
|
.map(|e| (e.name.clone(), e.is_dir, e.is_cagire()))
|
||||||
.collect();
|
.collect();
|
||||||
FileBrowserModal::new(title, &state.input, &entries)
|
FileBrowserModal::new(title, &state.input, &entries)
|
||||||
.selected(state.selected)
|
.selected(state.selected)
|
||||||
@@ -483,10 +483,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
|||||||
}
|
}
|
||||||
Modal::AddSamplePath(state) => {
|
Modal::AddSamplePath(state) => {
|
||||||
use crate::widgets::FileBrowserModal;
|
use crate::widgets::FileBrowserModal;
|
||||||
let entries: Vec<(String, bool)> = state
|
let entries: Vec<(String, bool, bool)> = state
|
||||||
.entries
|
.entries
|
||||||
.iter()
|
.iter()
|
||||||
.map(|e| (e.name.clone(), e.is_dir))
|
.map(|e| (e.name.clone(), e.is_dir, e.is_cagire()))
|
||||||
.collect();
|
.collect();
|
||||||
FileBrowserModal::new("Add Sample Path", &state.input, &entries)
|
FileBrowserModal::new("Add Sample Path", &state.input, &entries)
|
||||||
.selected(state.selected)
|
.selected(state.selected)
|
||||||
|
|||||||
Reference in New Issue
Block a user