Feat: optimizations
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -6,8 +6,20 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### Improved
|
### Improved
|
||||||
- Sample library browser: search now shows folder names only (no files) while typing, sorted by fuzzy match score. After confirming search with Enter, folders can be expanded and collapsed normally. Esc clears the search filter before closing the panel. Left arrow on a file collapses the parent folder. Cursor and scroll position stay valid after expand/collapse operations.
|
- Sample library browser: search now shows folder names only (no files) while typing, sorted by fuzzy match score. After confirming search with Enter, folders can be expanded and collapsed normally. Esc clears the search filter before closing the panel. Left arrow on a file collapses the parent folder. Cursor and scroll position stay valid after expand/collapse operations.
|
||||||
|
- RAM optimizations saving ~5 MB at startup plus smaller enums and fewer hot-path allocations:
|
||||||
|
- Removed dead `Step::command` field (~3.1 MB)
|
||||||
|
- Narrowed `Step::source` from `Option<usize>` to `Option<u8>` (~1.8 MB)
|
||||||
|
- `Op::SetParam` and `Op::GetContext` now use `&'static str` instead of `String`
|
||||||
|
- `SourceSpan` fields narrowed from `usize` to `u32`
|
||||||
|
- Dirty pattern tracking uses fixed `[[bool; 32]; 32]` array instead of `HashSet`
|
||||||
|
- Boxed `FileBrowserState` in `Modal` enum to shrink all variants
|
||||||
|
- `StepContext::cc_access` borrows instead of cloning `Arc<dyn CcAccess>`
|
||||||
|
- Removed unnecessary `Arc` wrapper from `Stack` type
|
||||||
|
- Variable key cache computes on-demand with reusable buffers instead of pre-allocating 2048 Strings
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Header bar is now always 3 lines tall with vertically centered content and full-height background colors, replacing the previous 1-or-2-line width-dependent layout.
|
||||||
|
- Help view Welcome page: BigText title is now gated behind `cfg(not(feature = "desktop"))`, falling back to a plain text title in the desktop build (same strategy as the splash screen).
|
||||||
- Space now toggles play/pause on all views, including the Patterns page where it previously toggled pattern play. Pattern play on the Patterns page is now bound to `p`.
|
- Space now toggles play/pause on all views, including the Patterns page where it previously toggled pattern play. Pattern play on the Patterns page is now bound to `p`.
|
||||||
|
|
||||||
## [0.0.7] - 2026-05-02
|
## [0.0.7] - 2026-05-02
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ fn tokenize(input: &str) -> Vec<Token> {
|
|||||||
}
|
}
|
||||||
s.push(ch);
|
s.push(ch);
|
||||||
}
|
}
|
||||||
tokens.push(Token::Str(s, SourceSpan { start, end }));
|
tokens.push(Token::Str(s, SourceSpan { start: start as u32, end: end as u32 }));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,8 +66,8 @@ fn tokenize(input: &str) -> Vec<Token> {
|
|||||||
tokens.push(Token::Word(
|
tokens.push(Token::Word(
|
||||||
";".to_string(),
|
";".to_string(),
|
||||||
SourceSpan {
|
SourceSpan {
|
||||||
start: pos,
|
start: pos as u32,
|
||||||
end: pos + 1,
|
end: (pos + 1) as u32,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
@@ -85,7 +85,7 @@ fn tokenize(input: &str) -> Vec<Token> {
|
|||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
let span = SourceSpan { start, end };
|
let span = SourceSpan { start: start as u32, end: end as u32 };
|
||||||
|
|
||||||
// Normalize shorthand float syntax: .25 -> 0.25, -.5 -> -0.5
|
// Normalize shorthand float syntax: .25 -> 0.25, -.5 -> -0.5
|
||||||
let word_to_parse = if word.starts_with('.')
|
let word_to_parse = if word.starts_with('.')
|
||||||
|
|||||||
@@ -60,11 +60,11 @@ pub enum Op {
|
|||||||
BranchIfZero(usize, Option<SourceSpan>, Option<SourceSpan>),
|
BranchIfZero(usize, Option<SourceSpan>, Option<SourceSpan>),
|
||||||
Branch(usize),
|
Branch(usize),
|
||||||
NewCmd,
|
NewCmd,
|
||||||
SetParam(String),
|
SetParam(&'static str),
|
||||||
Emit,
|
Emit,
|
||||||
Get,
|
Get,
|
||||||
Set,
|
Set,
|
||||||
GetContext(String),
|
GetContext(&'static str),
|
||||||
Rand,
|
Rand,
|
||||||
ExpRand,
|
ExpRand,
|
||||||
LogRand,
|
LogRand,
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ pub trait CcAccess: Send + Sync {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct SourceSpan {
|
pub struct SourceSpan {
|
||||||
pub start: usize,
|
pub start: u32,
|
||||||
pub end: usize,
|
pub end: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@@ -37,7 +37,7 @@ pub struct StepContext<'a> {
|
|||||||
pub speed: f64,
|
pub speed: f64,
|
||||||
pub fill: bool,
|
pub fill: bool,
|
||||||
pub nudge_secs: f64,
|
pub nudge_secs: f64,
|
||||||
pub cc_access: Option<Arc<dyn CcAccess>>,
|
pub cc_access: Option<&'a dyn CcAccess>,
|
||||||
pub speed_key: &'a str,
|
pub speed_key: &'a str,
|
||||||
pub chain_key: &'a str,
|
pub chain_key: &'a str,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
@@ -58,8 +58,8 @@ pub type VariablesMap = HashMap<String, Value>;
|
|||||||
pub type Variables = Arc<ArcSwap<VariablesMap>>;
|
pub type Variables = Arc<ArcSwap<VariablesMap>>;
|
||||||
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
|
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
|
||||||
pub type Rng = Arc<Mutex<StdRng>>;
|
pub type Rng = Arc<Mutex<StdRng>>;
|
||||||
pub type Stack = Arc<Mutex<Vec<Value>>>;
|
pub type Stack = Mutex<Vec<Value>>;
|
||||||
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(String, Value)]);
|
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(&'static str, Value)]);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
@@ -138,7 +138,7 @@ impl Value {
|
|||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub(super) struct CmdRegister {
|
pub(super) struct CmdRegister {
|
||||||
sound: Option<Value>,
|
sound: Option<Value>,
|
||||||
params: Vec<(String, Value)>,
|
params: Vec<(&'static str, Value)>,
|
||||||
deltas: Vec<Value>,
|
deltas: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ impl CmdRegister {
|
|||||||
self.sound = Some(val);
|
self.sound = Some(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn set_param(&mut self, key: String, val: Value) {
|
pub(super) fn set_param(&mut self, key: &'static str, val: Value) {
|
||||||
self.params.push((key, val));
|
self.params.push((key, val));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ impl CmdRegister {
|
|||||||
self.sound.as_ref()
|
self.sound.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn params(&self) -> &[(String, Value)] {
|
pub(super) fn params(&self) -> &[(&'static str, Value)] {
|
||||||
&self.params
|
&self.params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub struct Forth {
|
|||||||
impl Forth {
|
impl Forth {
|
||||||
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
|
pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stack: Arc::new(Mutex::new(Vec::new())),
|
stack: Mutex::new(Vec::new()),
|
||||||
vars,
|
vars,
|
||||||
dict,
|
dict,
|
||||||
rng,
|
rng,
|
||||||
@@ -227,7 +227,7 @@ impl Forth {
|
|||||||
Some(v) => Some(v.as_str()?.to_string()),
|
Some(v) => Some(v.as_str()?.to_string()),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let resolved_params: Vec<(String, String)> = params
|
let resolved_params: Vec<(&str, String)> = params
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| {
|
.map(|(k, v)| {
|
||||||
let resolved = resolve_cycling(v, emit_idx);
|
let resolved = resolve_cycling(v, emit_idx);
|
||||||
@@ -238,7 +238,7 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(k.clone(), resolved.to_param_string())
|
(*k, resolved.to_param_string())
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
emit_output(
|
emit_output(
|
||||||
@@ -555,7 +555,7 @@ impl Forth {
|
|||||||
} else {
|
} else {
|
||||||
Value::CycleList(Arc::from(values))
|
Value::CycleList(Arc::from(values))
|
||||||
};
|
};
|
||||||
cmd.set_param(param.clone(), val);
|
cmd.set_param(param, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::Emit => {
|
Op::Emit => {
|
||||||
@@ -613,7 +613,7 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Op::GetContext(name) => {
|
Op::GetContext(name) => {
|
||||||
let val = match name.as_str() {
|
let val = match *name {
|
||||||
"step" => Value::Int(ctx.step as i64, None),
|
"step" => Value::Int(ctx.step as i64, None),
|
||||||
"beat" => Value::Float(ctx.beat, None),
|
"beat" => Value::Float(ctx.beat, None),
|
||||||
"bank" => Value::Int(ctx.bank as i64, None),
|
"bank" => Value::Int(ctx.bank as i64, None),
|
||||||
@@ -879,8 +879,8 @@ impl Forth {
|
|||||||
return Err("tempo and speed must be non-zero".into());
|
return Err("tempo and speed must be non-zero".into());
|
||||||
}
|
}
|
||||||
let dur = beats * 60.0 / ctx.tempo / ctx.speed;
|
let dur = beats * 60.0 / ctx.tempo / ctx.speed;
|
||||||
cmd.set_param("fit".into(), Value::Float(dur, None));
|
cmd.set_param("fit", Value::Float(dur, None));
|
||||||
cmd.set_param("dur".into(), Value::Float(dur, None));
|
cmd.set_param("dur", Value::Float(dur, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::At => {
|
Op::At => {
|
||||||
@@ -896,18 +896,18 @@ impl Forth {
|
|||||||
let s = stack.pop().ok_or("stack underflow")?;
|
let s = stack.pop().ok_or("stack underflow")?;
|
||||||
let d = stack.pop().ok_or("stack underflow")?;
|
let d = stack.pop().ok_or("stack underflow")?;
|
||||||
let a = stack.pop().ok_or("stack underflow")?;
|
let a = stack.pop().ok_or("stack underflow")?;
|
||||||
cmd.set_param("attack".into(), a);
|
cmd.set_param("attack", a);
|
||||||
cmd.set_param("decay".into(), d);
|
cmd.set_param("decay", d);
|
||||||
cmd.set_param("sustain".into(), s);
|
cmd.set_param("sustain", s);
|
||||||
cmd.set_param("release".into(), r);
|
cmd.set_param("release", r);
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::Ad => {
|
Op::Ad => {
|
||||||
let d = stack.pop().ok_or("stack underflow")?;
|
let d = stack.pop().ok_or("stack underflow")?;
|
||||||
let a = stack.pop().ok_or("stack underflow")?;
|
let a = stack.pop().ok_or("stack underflow")?;
|
||||||
cmd.set_param("attack".into(), a);
|
cmd.set_param("attack", a);
|
||||||
cmd.set_param("decay".into(), d);
|
cmd.set_param("decay", d);
|
||||||
cmd.set_param("sustain".into(), Value::Int(0, None));
|
cmd.set_param("sustain", Value::Int(0, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
Op::Apply => {
|
Op::Apply => {
|
||||||
@@ -1055,14 +1055,14 @@ impl Forth {
|
|||||||
params
|
params
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find(|(k, _)| k == name)
|
.find(|(k, _)| *k == name)
|
||||||
.and_then(|(_, v)| v.as_int().ok())
|
.and_then(|(_, v)| v.as_int().ok())
|
||||||
};
|
};
|
||||||
let get_float = |name: &str| -> Option<f64> {
|
let get_float = |name: &str| -> Option<f64> {
|
||||||
params
|
params
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find(|(k, _)| k == name)
|
.find(|(k, _)| *k == name)
|
||||||
.and_then(|(_, v)| v.as_float().ok())
|
.and_then(|(_, v)| v.as_float().ok())
|
||||||
};
|
};
|
||||||
let chan = get_int("chan")
|
let chan = get_int("chan")
|
||||||
@@ -1140,11 +1140,11 @@ impl Forth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_dev_param(params: &[(String, Value)]) -> u8 {
|
fn extract_dev_param(params: &[(&str, Value)]) -> u8 {
|
||||||
params
|
params
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find(|(k, _)| k == "dev")
|
.find(|(k, _)| *k == "dev")
|
||||||
.and_then(|(_, v)| v.as_int().ok())
|
.and_then(|(_, v)| v.as_int().ok())
|
||||||
.map(|d| d.clamp(0, 3) as u8)
|
.map(|d| d.clamp(0, 3) as u8)
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
@@ -1181,7 +1181,7 @@ fn is_tempo_scaled_param(name: &str) -> bool {
|
|||||||
|
|
||||||
fn emit_output(
|
fn emit_output(
|
||||||
sound: Option<&str>,
|
sound: Option<&str>,
|
||||||
params: &[(String, String)],
|
params: &[(&str, String)],
|
||||||
step_duration: f64,
|
step_duration: f64,
|
||||||
nudge_secs: f64,
|
nudge_secs: f64,
|
||||||
outputs: &mut Vec<String>,
|
outputs: &mut Vec<String>,
|
||||||
@@ -1190,8 +1190,8 @@ fn emit_output(
|
|||||||
let mut out = String::with_capacity(128);
|
let mut out = String::with_capacity(128);
|
||||||
out.push('/');
|
out.push('/');
|
||||||
|
|
||||||
let has_dur = params.iter().any(|(k, _)| k == "dur");
|
let has_dur = params.iter().any(|(k, _)| *k == "dur");
|
||||||
let delaytime_idx = params.iter().position(|(k, _)| k == "delaytime");
|
let delaytime_idx = params.iter().position(|(k, _)| *k == "delaytime");
|
||||||
|
|
||||||
if let Some(s) = sound {
|
if let Some(s) = sound {
|
||||||
let _ = write!(&mut out, "sound/{s}");
|
let _ = write!(&mut out, "sound/{s}");
|
||||||
|
|||||||
@@ -216,8 +216,8 @@ pub(crate) fn compile_word(
|
|||||||
ops.push(op);
|
ops.push(op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Context(ctx) => ops.push(Op::GetContext((*ctx).into())),
|
Context(ctx) => ops.push(Op::GetContext(ctx)),
|
||||||
Param => ops.push(Op::SetParam(word.name.into())),
|
Param => ops.push(Op::SetParam(word.name)),
|
||||||
Probability(p) => {
|
Probability(p) => {
|
||||||
ops.push(Op::PushFloat(*p, None));
|
ops.push(Op::PushFloat(*p, None));
|
||||||
ops.push(Op::ChanceExec);
|
ops.push(Op::ChanceExec);
|
||||||
|
|||||||
@@ -210,10 +210,8 @@ impl SyncMode {
|
|||||||
pub struct Step {
|
pub struct Step {
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub script: String,
|
pub script: String,
|
||||||
#[serde(skip)]
|
|
||||||
pub command: Option<String>,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub source: Option<usize>,
|
pub source: Option<u8>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -229,7 +227,6 @@ impl Default for Step {
|
|||||||
Self {
|
Self {
|
||||||
active: true,
|
active: true,
|
||||||
script: String::new(),
|
script: String::new(),
|
||||||
command: None,
|
|
||||||
source: None,
|
source: None,
|
||||||
name: None,
|
name: None,
|
||||||
}
|
}
|
||||||
@@ -254,7 +251,7 @@ struct SparseStep {
|
|||||||
#[serde(default, skip_serializing_if = "String::is_empty")]
|
#[serde(default, skip_serializing_if = "String::is_empty")]
|
||||||
script: String,
|
script: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
source: Option<usize>,
|
source: Option<u8>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
@@ -348,7 +345,6 @@ impl<'de> Deserialize<'de> for Pattern {
|
|||||||
steps[ss.i] = Step {
|
steps[ss.i] = Step {
|
||||||
active: ss.active,
|
active: ss.active,
|
||||||
script: ss.script,
|
script: ss.script,
|
||||||
command: None,
|
|
||||||
source: ss.source,
|
source: ss.source,
|
||||||
name: ss.name,
|
name: ss.name,
|
||||||
};
|
};
|
||||||
@@ -410,7 +406,7 @@ impl Pattern {
|
|||||||
for _ in 0..self.steps.len() {
|
for _ in 0..self.steps.len() {
|
||||||
if let Some(step) = self.steps.get(current) {
|
if let Some(step) = self.steps.get(current) {
|
||||||
if let Some(source) = step.source {
|
if let Some(source) = step.source {
|
||||||
current = source;
|
current = source as usize;
|
||||||
} else {
|
} else {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|||||||
56
src/app.rs
56
src/app.rs
@@ -419,44 +419,16 @@ impl App {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if script.trim().is_empty() {
|
if script.trim().is_empty() {
|
||||||
if let Some(step) = self
|
|
||||||
.project_state
|
|
||||||
.project
|
|
||||||
.pattern_at_mut(bank, pattern)
|
|
||||||
.step_mut(step_idx)
|
|
||||||
{
|
|
||||||
step.command = None;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = self.create_step_context(step_idx, link);
|
let ctx = self.create_step_context(step_idx, link);
|
||||||
|
|
||||||
match self.script_engine.evaluate(&script, &ctx) {
|
match self.script_engine.evaluate(&script, &ctx) {
|
||||||
Ok(cmds) => {
|
Ok(_) => {
|
||||||
if let Some(step) = self
|
|
||||||
.project_state
|
|
||||||
.project
|
|
||||||
.pattern_at_mut(bank, pattern)
|
|
||||||
.step_mut(step_idx)
|
|
||||||
{
|
|
||||||
step.command = if cmds.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(cmds.join("\n"))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
self.ui.flash("Script compiled", 150, FlashKind::Info);
|
self.ui.flash("Script compiled", 150, FlashKind::Info);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(step) = self
|
|
||||||
.project_state
|
|
||||||
.project
|
|
||||||
.pattern_at_mut(bank, pattern)
|
|
||||||
.step_mut(step_idx)
|
|
||||||
{
|
|
||||||
step.command = None;
|
|
||||||
}
|
|
||||||
self.ui
|
self.ui
|
||||||
.flash(&format!("Script error: {e}"), 300, FlashKind::Error);
|
.flash(&format!("Script error: {e}"), 300, FlashKind::Error);
|
||||||
}
|
}
|
||||||
@@ -477,33 +449,11 @@ impl App {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if script.trim().is_empty() {
|
if script.trim().is_empty() {
|
||||||
if let Some(step) = self
|
|
||||||
.project_state
|
|
||||||
.project
|
|
||||||
.pattern_at_mut(bank, pattern)
|
|
||||||
.step_mut(step_idx)
|
|
||||||
{
|
|
||||||
step.command = None;
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = self.create_step_context(step_idx, link);
|
let ctx = self.create_step_context(step_idx, link);
|
||||||
|
let _ = self.script_engine.evaluate(&script, &ctx);
|
||||||
if let Ok(cmds) = self.script_engine.evaluate(&script, &ctx) {
|
|
||||||
if let Some(step) = self
|
|
||||||
.project_state
|
|
||||||
.project
|
|
||||||
.pattern_at_mut(bank, pattern)
|
|
||||||
.step_mut(step_idx)
|
|
||||||
{
|
|
||||||
step.command = if cmds.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(cmds.join("\n"))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,7 +1004,7 @@ impl App {
|
|||||||
[self.editor_ctx.pattern];
|
[self.editor_ctx.pattern];
|
||||||
if let Some(source) = pattern.step(self.editor_ctx.step).and_then(|s| s.source)
|
if let Some(source) = pattern.step(self.editor_ctx.step).and_then(|s| s.source)
|
||||||
{
|
{
|
||||||
self.editor_ctx.step = source;
|
self.editor_ctx.step = source as usize;
|
||||||
}
|
}
|
||||||
self.load_step_to_editor();
|
self.load_step_to_editor();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ pub struct PatternSnapshot {
|
|||||||
pub struct StepSnapshot {
|
pub struct StepSnapshot {
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub script: String,
|
pub script: String,
|
||||||
pub source: Option<usize>,
|
pub source: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Debug)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
@@ -392,7 +392,7 @@ impl PatternSnapshot {
|
|||||||
for _ in 0..self.steps.len() {
|
for _ in 0..self.steps.len() {
|
||||||
if let Some(step) = self.steps.get(current) {
|
if let Some(step) = self.steps.get(current) {
|
||||||
if let Some(source) = step.source {
|
if let Some(source) = step.source {
|
||||||
current = source;
|
current = source as usize;
|
||||||
} else {
|
} else {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
@@ -500,30 +500,32 @@ fn parse_chain_target(s: &str) -> Option<PatternId> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KeyCache {
|
struct KeyBuf {
|
||||||
speed_keys: [[String; MAX_PATTERNS]; MAX_BANKS],
|
speed: String,
|
||||||
chain_keys: [[String; MAX_PATTERNS]; MAX_BANKS],
|
chain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyCache {
|
impl KeyBuf {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
speed_keys: std::array::from_fn(|bank| {
|
speed: String::with_capacity(24),
|
||||||
std::array::from_fn(|pattern| format!("__speed_{bank}_{pattern}__"))
|
chain: String::with_capacity(24),
|
||||||
}),
|
|
||||||
chain_keys: std::array::from_fn(|bank| {
|
|
||||||
std::array::from_fn(|pattern| format!("__chain_{bank}_{pattern}__"))
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn speed_key(&self, bank: usize, pattern: usize) -> &str {
|
fn format_speed_key(buf: &mut String, bank: usize, pattern: usize) -> &str {
|
||||||
&self.speed_keys[bank][pattern]
|
use std::fmt::Write;
|
||||||
}
|
buf.clear();
|
||||||
|
write!(buf, "__speed_{bank}_{pattern}__").unwrap();
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
fn chain_key(&self, bank: usize, pattern: usize) -> &str {
|
fn format_chain_key(buf: &mut String, bank: usize, pattern: usize) -> &str {
|
||||||
&self.chain_keys[bank][pattern]
|
use std::fmt::Write;
|
||||||
}
|
buf.clear();
|
||||||
|
write!(buf, "__chain_{bank}_{pattern}__").unwrap();
|
||||||
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct SequencerState {
|
pub(crate) struct SequencerState {
|
||||||
@@ -537,7 +539,7 @@ pub(crate) struct SequencerState {
|
|||||||
variables: Variables,
|
variables: Variables,
|
||||||
dict: Dictionary,
|
dict: Dictionary,
|
||||||
speed_overrides: HashMap<(usize, usize), f64>,
|
speed_overrides: HashMap<(usize, usize), f64>,
|
||||||
key_cache: KeyCache,
|
key_buf: KeyBuf,
|
||||||
buf_audio_commands: Vec<TimestampedCommand>,
|
buf_audio_commands: Vec<TimestampedCommand>,
|
||||||
buf_activated: Vec<PatternId>,
|
buf_activated: Vec<PatternId>,
|
||||||
buf_stopped: Vec<PatternId>,
|
buf_stopped: Vec<PatternId>,
|
||||||
@@ -566,7 +568,7 @@ impl SequencerState {
|
|||||||
variables,
|
variables,
|
||||||
dict,
|
dict,
|
||||||
speed_overrides: HashMap::with_capacity(MAX_PATTERNS),
|
speed_overrides: HashMap::with_capacity(MAX_PATTERNS),
|
||||||
key_cache: KeyCache::new(),
|
key_buf: KeyBuf::new(),
|
||||||
buf_audio_commands: Vec::with_capacity(32),
|
buf_audio_commands: Vec::with_capacity(32),
|
||||||
buf_activated: Vec::with_capacity(16),
|
buf_activated: Vec::with_capacity(16),
|
||||||
buf_stopped: Vec::with_capacity(16),
|
buf_stopped: Vec::with_capacity(16),
|
||||||
@@ -834,7 +836,7 @@ impl SequencerState {
|
|||||||
{
|
{
|
||||||
let vars = self.variables.load();
|
let vars = self.variables.load();
|
||||||
for id in self.audio_state.active_patterns.keys() {
|
for id in self.audio_state.active_patterns.keys() {
|
||||||
let key = self.key_cache.speed_key(id.bank, id.pattern);
|
let key = format_speed_key(&mut self.key_buf.speed, id.bank, id.pattern);
|
||||||
if let Some(v) = vars.get(key).and_then(|v: &Value| v.as_float().ok()) {
|
if let Some(v) = vars.get(key).and_then(|v: &Value| v.as_float().ok()) {
|
||||||
self.speed_overrides.insert((id.bank, id.pattern), v);
|
self.speed_overrides.insert((id.bank, id.pattern), v);
|
||||||
}
|
}
|
||||||
@@ -884,6 +886,8 @@ impl SequencerState {
|
|||||||
active.pattern,
|
active.pattern,
|
||||||
source_idx,
|
source_idx,
|
||||||
);
|
);
|
||||||
|
let speed_key = format_speed_key(&mut self.key_buf.speed, active.bank, active.pattern);
|
||||||
|
let chain_key = format_chain_key(&mut self.key_buf.chain, active.bank, active.pattern);
|
||||||
let ctx = StepContext {
|
let ctx = StepContext {
|
||||||
step: step_idx,
|
step: step_idx,
|
||||||
beat: step_beat,
|
beat: step_beat,
|
||||||
@@ -897,9 +901,9 @@ impl SequencerState {
|
|||||||
speed: speed_mult,
|
speed: speed_mult,
|
||||||
fill,
|
fill,
|
||||||
nudge_secs,
|
nudge_secs,
|
||||||
cc_access: self.cc_access.clone(),
|
cc_access: self.cc_access.as_deref(),
|
||||||
speed_key: self.key_cache.speed_key(active.bank, active.pattern),
|
speed_key,
|
||||||
chain_key: self.key_cache.chain_key(active.bank, active.pattern),
|
chain_key,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
mouse_x,
|
mouse_x,
|
||||||
#[cfg(feature = "desktop")]
|
#[cfg(feature = "desktop")]
|
||||||
@@ -970,8 +974,9 @@ impl SequencerState {
|
|||||||
.and_then(|v: &Value| v.as_float().ok());
|
.and_then(|v: &Value| v.as_float().ok());
|
||||||
|
|
||||||
let mut chain_transitions = Vec::new();
|
let mut chain_transitions = Vec::new();
|
||||||
|
let mut buf = String::with_capacity(24);
|
||||||
for id in completed {
|
for id in completed {
|
||||||
let chain_key = self.key_cache.chain_key(id.bank, id.pattern);
|
let chain_key = format_chain_key(&mut buf, id.bank, id.pattern);
|
||||||
if let Some(Value::Str(s, _)) = vars.get(chain_key) {
|
if let Some(Value::Str(s, _)) = vars.get(chain_key) {
|
||||||
if let Some(target) = parse_chain_target(s) {
|
if let Some(target) = parse_chain_target(s) {
|
||||||
chain_transitions.push((*id, target));
|
chain_transitions.push((*id, target));
|
||||||
@@ -980,24 +985,24 @@ impl SequencerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove consumed variables (tempo and chain keys)
|
// Remove consumed variables (tempo and chain keys)
|
||||||
let needs_removal = new_tempo.is_some()
|
let mut needs_removal = new_tempo.is_some();
|
||||||
|| completed.iter().any(|id| {
|
if !needs_removal {
|
||||||
let chain_key = self.key_cache.chain_key(id.bank, id.pattern);
|
for id in completed.iter().chain(stopped.iter()) {
|
||||||
vars.contains_key(chain_key)
|
if vars.contains_key(format_chain_key(&mut buf, id.bank, id.pattern)) {
|
||||||
})
|
needs_removal = true;
|
||||||
|| stopped.iter().any(|id| {
|
break;
|
||||||
let chain_key = self.key_cache.chain_key(id.bank, id.pattern);
|
}
|
||||||
vars.contains_key(chain_key)
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
if needs_removal {
|
if needs_removal {
|
||||||
let mut new_vars = (**vars).clone();
|
let mut new_vars = (**vars).clone();
|
||||||
new_vars.remove("__tempo__");
|
new_vars.remove("__tempo__");
|
||||||
for id in completed {
|
for id in completed {
|
||||||
new_vars.remove(self.key_cache.chain_key(id.bank, id.pattern));
|
new_vars.remove(format_chain_key(&mut buf, id.bank, id.pattern));
|
||||||
}
|
}
|
||||||
for id in stopped {
|
for id in stopped {
|
||||||
new_vars.remove(self.key_cache.chain_key(id.bank, id.pattern));
|
new_vars.remove(format_chain_key(&mut buf, id.bank, id.pattern));
|
||||||
}
|
}
|
||||||
self.variables.store(Arc::new(new_vars));
|
self.variables.store(Arc::new(new_vars));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -979,7 +979,7 @@ fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool) -> InputR
|
|||||||
.map(|p| p.display().to_string())
|
.map(|p| p.display().to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let state = FileBrowserState::new_save(initial);
|
let state = FileBrowserState::new_save(initial);
|
||||||
ctx.dispatch(AppCommand::OpenModal(Modal::FileBrowser(state)));
|
ctx.dispatch(AppCommand::OpenModal(Modal::FileBrowser(Box::new(state))));
|
||||||
}
|
}
|
||||||
KeyCode::Char('c') if ctrl => {
|
KeyCode::Char('c') if ctrl => {
|
||||||
ctx.dispatch(AppCommand::CopySteps);
|
ctx.dispatch(AppCommand::CopySteps);
|
||||||
@@ -1011,7 +1011,7 @@ fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool) -> InputR
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let state = FileBrowserState::new_load(default_dir);
|
let state = FileBrowserState::new_load(default_dir);
|
||||||
ctx.dispatch(AppCommand::OpenModal(Modal::FileBrowser(state)));
|
ctx.dispatch(AppCommand::OpenModal(Modal::FileBrowser(Box::new(state))));
|
||||||
}
|
}
|
||||||
KeyCode::Char('+') | KeyCode::Char('=') => ctx.dispatch(AppCommand::TempoUp),
|
KeyCode::Char('+') | KeyCode::Char('=') => ctx.dispatch(AppCommand::TempoUp),
|
||||||
KeyCode::Char('-') => ctx.dispatch(AppCommand::TempoDown),
|
KeyCode::Char('-') => ctx.dispatch(AppCommand::TempoDown),
|
||||||
@@ -1422,7 +1422,7 @@ fn handle_engine_page(ctx: &mut InputContext, key: KeyEvent) -> InputResult {
|
|||||||
KeyCode::Char('A') => {
|
KeyCode::Char('A') => {
|
||||||
use crate::state::file_browser::FileBrowserState;
|
use crate::state::file_browser::FileBrowserState;
|
||||||
let state = FileBrowserState::new_load(String::new());
|
let state = FileBrowserState::new_load(String::new());
|
||||||
ctx.dispatch(AppCommand::OpenModal(Modal::AddSamplePath(state)));
|
ctx.dispatch(AppCommand::OpenModal(Modal::AddSamplePath(Box::new(state))));
|
||||||
}
|
}
|
||||||
KeyCode::Char('D') => {
|
KeyCode::Char('D') => {
|
||||||
if ctx.app.audio.section == EngineSection::Samples {
|
if ctx.app.audio.section == EngineSection::Samples {
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ pub fn paste_steps(
|
|||||||
step.name = data.name.clone();
|
step.name = data.name.clone();
|
||||||
if source.is_some() {
|
if source.is_some() {
|
||||||
step.script.clear();
|
step.script.clear();
|
||||||
step.command = None;
|
|
||||||
} else {
|
} else {
|
||||||
step.script = data.script.clone();
|
step.script = data.script.clone();
|
||||||
}
|
}
|
||||||
@@ -130,15 +129,14 @@ pub fn link_paste_steps(
|
|||||||
let source_idx = if data.source.is_some() {
|
let source_idx = if data.source.is_some() {
|
||||||
data.source
|
data.source
|
||||||
} else {
|
} else {
|
||||||
Some(data.original_index)
|
Some(data.original_index as u8)
|
||||||
};
|
};
|
||||||
if source_idx == Some(target) {
|
if source_idx == Some(target as u8) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(step) = project.pattern_at_mut(bank, pattern).step_mut(target) {
|
if let Some(step) = project.pattern_at_mut(bank, pattern).step_mut(target) {
|
||||||
step.source = source_idx;
|
step.source = source_idx;
|
||||||
step.script.clear();
|
step.script.clear();
|
||||||
step.command = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +181,7 @@ pub fn duplicate_steps(
|
|||||||
let pat_len = pat.length;
|
let pat_len = pat.length;
|
||||||
let paste_at = *indices.last().unwrap() + 1;
|
let paste_at = *indices.last().unwrap() + 1;
|
||||||
|
|
||||||
let dupe_data: Vec<(bool, String, Option<usize>)> = indices
|
let dupe_data: Vec<(bool, String, Option<u8>)> = indices
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|&idx| {
|
.filter_map(|&idx| {
|
||||||
let step = pat.step(idx)?;
|
let step = pat.step(idx)?;
|
||||||
@@ -204,10 +202,8 @@ pub fn duplicate_steps(
|
|||||||
step.source = source;
|
step.source = source;
|
||||||
if source.is_some() {
|
if source.is_some() {
|
||||||
step.script.clear();
|
step.script.clear();
|
||||||
step.command = None;
|
|
||||||
} else {
|
} else {
|
||||||
step.script = script;
|
step.script = script;
|
||||||
step.command = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compile_targets.push(target);
|
compile_targets.push(target);
|
||||||
|
|||||||
@@ -44,9 +44,8 @@ pub fn apply_distribution(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(step) = project.pattern_at_mut(bank, pattern).step_mut(target) {
|
if let Some(step) = project.pattern_at_mut(bank, pattern).step_mut(target) {
|
||||||
step.source = Some(source_step);
|
step.source = Some(source_step as u8);
|
||||||
step.script.clear();
|
step.script.clear();
|
||||||
step.command = None;
|
|
||||||
step.active = true;
|
step.active = true;
|
||||||
}
|
}
|
||||||
targets.push(target);
|
targets.push(target);
|
||||||
|
|||||||
@@ -94,16 +94,14 @@ pub fn get_step_script(
|
|||||||
pub fn delete_step(project: &mut Project, bank: usize, pattern: usize, step: usize) -> PatternEdit {
|
pub fn delete_step(project: &mut Project, bank: usize, pattern: usize, step: usize) -> PatternEdit {
|
||||||
let pat = project.pattern_at_mut(bank, pattern);
|
let pat = project.pattern_at_mut(bank, pattern);
|
||||||
for s in &mut pat.steps {
|
for s in &mut pat.steps {
|
||||||
if s.source == Some(step) {
|
if s.source == Some(step as u8) {
|
||||||
s.source = None;
|
s.source = None;
|
||||||
s.script.clear();
|
s.script.clear();
|
||||||
s.command = None;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
set_step_script(project, bank, pattern, step, String::new());
|
set_step_script(project, bank, pattern, step, String::new());
|
||||||
if let Some(s) = project.pattern_at_mut(bank, pattern).step_mut(step) {
|
if let Some(s) = project.pattern_at_mut(bank, pattern).step_mut(step) {
|
||||||
s.command = None;
|
|
||||||
s.source = None;
|
s.source = None;
|
||||||
}
|
}
|
||||||
PatternEdit::new(bank, pattern)
|
PatternEdit::new(bank, pattern)
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ pub struct CopiedSteps {
|
|||||||
pub struct CopiedStepData {
|
pub struct CopiedStepData {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub source: Option<usize>,
|
pub source: Option<u8>,
|
||||||
pub original_index: usize,
|
pub original_index: usize,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ pub enum Modal {
|
|||||||
bank: usize,
|
bank: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
},
|
},
|
||||||
FileBrowser(FileBrowserState),
|
FileBrowser(Box<FileBrowserState>),
|
||||||
RenameBank {
|
RenameBank {
|
||||||
bank: usize,
|
bank: usize,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -50,7 +50,7 @@ pub enum Modal {
|
|||||||
input: String,
|
input: String,
|
||||||
},
|
},
|
||||||
SetTempo(String),
|
SetTempo(String),
|
||||||
AddSamplePath(FileBrowserState),
|
AddSamplePath(Box<FileBrowserState>),
|
||||||
Editor,
|
Editor,
|
||||||
Preview,
|
Preview,
|
||||||
PatternProps {
|
PatternProps {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::model::{MAX_BANKS, MAX_PATTERNS};
|
use crate::model::{MAX_BANKS, MAX_PATTERNS};
|
||||||
@@ -7,7 +6,7 @@ use crate::model::Project;
|
|||||||
pub struct ProjectState {
|
pub struct ProjectState {
|
||||||
pub project: Project,
|
pub project: Project,
|
||||||
pub file_path: Option<PathBuf>,
|
pub file_path: Option<PathBuf>,
|
||||||
pub dirty_patterns: HashSet<(usize, usize)>,
|
dirty_patterns: [[bool; MAX_PATTERNS]; MAX_BANKS],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProjectState {
|
impl Default for ProjectState {
|
||||||
@@ -15,7 +14,7 @@ impl Default for ProjectState {
|
|||||||
let mut state = Self {
|
let mut state = Self {
|
||||||
project: Project::default(),
|
project: Project::default(),
|
||||||
file_path: None,
|
file_path: None,
|
||||||
dirty_patterns: HashSet::new(),
|
dirty_patterns: [[false; MAX_PATTERNS]; MAX_BANKS],
|
||||||
};
|
};
|
||||||
state.mark_all_dirty();
|
state.mark_all_dirty();
|
||||||
state
|
state
|
||||||
@@ -24,18 +23,23 @@ impl Default for ProjectState {
|
|||||||
|
|
||||||
impl ProjectState {
|
impl ProjectState {
|
||||||
pub fn mark_dirty(&mut self, bank: usize, pattern: usize) {
|
pub fn mark_dirty(&mut self, bank: usize, pattern: usize) {
|
||||||
self.dirty_patterns.insert((bank, pattern));
|
self.dirty_patterns[bank][pattern] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark_all_dirty(&mut self) {
|
pub fn mark_all_dirty(&mut self) {
|
||||||
for bank in 0..MAX_BANKS {
|
self.dirty_patterns = [[true; MAX_PATTERNS]; MAX_BANKS];
|
||||||
for pattern in 0..MAX_PATTERNS {
|
|
||||||
self.dirty_patterns.insert((bank, pattern));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_dirty(&mut self) -> HashSet<(usize, usize)> {
|
pub fn take_dirty(&mut self) -> Vec<(usize, usize)> {
|
||||||
std::mem::take(&mut self.dirty_patterns)
|
let mut result = Vec::new();
|
||||||
|
for (bank, patterns) in self.dirty_patterns.iter_mut().enumerate() {
|
||||||
|
for (pattern, dirty) in patterns.iter_mut().enumerate() {
|
||||||
|
if *dirty {
|
||||||
|
*dirty = false;
|
||||||
|
result.push((bank, pattern));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use ratatui::style::{Color, Modifier, Style};
|
|||||||
use ratatui::text::{Line as RLine, Span};
|
use ratatui::text::{Line as RLine, Span};
|
||||||
use ratatui::widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Wrap};
|
use ratatui::widgets::{Block, Borders, List, ListItem, Padding, Paragraph, Wrap};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
|
#[cfg(not(feature = "desktop"))]
|
||||||
use tui_big_text::{BigText, PixelSize};
|
use tui_big_text::{BigText, PixelSize};
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
@@ -173,7 +174,10 @@ fn render_topics(frame: &mut Frame, app: &App, area: Rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WELCOME_TOPIC: usize = 0;
|
const WELCOME_TOPIC: usize = 0;
|
||||||
|
#[cfg(not(feature = "desktop"))]
|
||||||
const BIG_TITLE_HEIGHT: u16 = 6;
|
const BIG_TITLE_HEIGHT: u16 = 6;
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
const BIG_TITLE_HEIGHT: u16 = 3;
|
||||||
|
|
||||||
fn render_content(frame: &mut Frame, app: &App, area: Rect) {
|
fn render_content(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
let theme = theme::get();
|
let theme = theme::get();
|
||||||
@@ -186,19 +190,34 @@ fn render_content(frame: &mut Frame, app: &App, area: Rect) {
|
|||||||
let [title_area, rest] =
|
let [title_area, rest] =
|
||||||
Layout::vertical([Constraint::Length(BIG_TITLE_HEIGHT), Constraint::Fill(1)])
|
Layout::vertical([Constraint::Length(BIG_TITLE_HEIGHT), Constraint::Fill(1)])
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "desktop"))]
|
||||||
let big_title = BigText::builder()
|
let big_title = BigText::builder()
|
||||||
.pixel_size(PixelSize::Quadrant)
|
.pixel_size(PixelSize::Quadrant)
|
||||||
.style(Style::new().fg(theme.markdown.h1).bold())
|
.style(Style::new().fg(theme.markdown.h1).bold())
|
||||||
.lines(vec!["CAGIRE".into()])
|
.lines(vec!["CAGIRE".into()])
|
||||||
.centered()
|
.centered()
|
||||||
.build();
|
.build();
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
let big_title = Paragraph::new(RLine::from(Span::styled(
|
||||||
|
"CAGIRE",
|
||||||
|
Style::new().fg(theme.markdown.h1).bold(),
|
||||||
|
)))
|
||||||
|
.alignment(ratatui::layout::Alignment::Center);
|
||||||
|
|
||||||
let subtitle = Paragraph::new(RLine::from(Span::styled(
|
let subtitle = Paragraph::new(RLine::from(Span::styled(
|
||||||
"A Forth Sequencer",
|
"A Forth Sequencer",
|
||||||
Style::new().fg(theme.ui.text_primary),
|
Style::new().fg(theme.ui.text_primary),
|
||||||
)))
|
)))
|
||||||
.alignment(ratatui::layout::Alignment::Center);
|
.alignment(ratatui::layout::Alignment::Center);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "desktop"))]
|
||||||
let [big_area, subtitle_area] =
|
let [big_area, subtitle_area] =
|
||||||
Layout::vertical([Constraint::Length(4), Constraint::Length(2)]).areas(title_area);
|
Layout::vertical([Constraint::Length(4), Constraint::Length(2)]).areas(title_area);
|
||||||
|
#[cfg(feature = "desktop")]
|
||||||
|
let [big_area, subtitle_area] =
|
||||||
|
Layout::vertical([Constraint::Length(1), Constraint::Length(2)]).areas(title_area);
|
||||||
|
|
||||||
frame.render_widget(big_title, big_area);
|
frame.render_widget(big_title, big_area);
|
||||||
frame.render_widget(subtitle, subtitle_area);
|
frame.render_widget(subtitle, subtitle_area);
|
||||||
rest
|
rest
|
||||||
|
|||||||
@@ -212,10 +212,10 @@ pub fn highlight_line_with_runtime(
|
|||||||
|
|
||||||
let is_selected = selected_spans
|
let is_selected = selected_spans
|
||||||
.iter()
|
.iter()
|
||||||
.any(|span| overlaps(token.start, token.end, span.start, span.end));
|
.any(|span| overlaps(token.start, token.end, span.start as usize, span.end as usize));
|
||||||
let is_executed = executed_spans
|
let is_executed = executed_spans
|
||||||
.iter()
|
.iter()
|
||||||
.any(|span| overlaps(token.start, token.end, span.start, span.end));
|
.any(|span| overlaps(token.start, token.end, span.start as usize, span.end as usize));
|
||||||
|
|
||||||
let mut style = token.kind.style();
|
let mut style = token.kind.style();
|
||||||
if token.varargs {
|
if token.varargs {
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ fn render_tile(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let link_color = step.and_then(|s| s.source).map(|src| {
|
let link_color = step.and_then(|s| s.source).map(|src| {
|
||||||
let i = src % 5;
|
let i = src as usize % 5;
|
||||||
(theme.tile.link_bright[i], theme.tile.link_dim[i])
|
(theme.tile.link_bright[i], theme.tile.link_dim[i])
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -236,7 +236,7 @@ fn render_tile(
|
|||||||
|
|
||||||
// For linked steps, get the name from the source step
|
// For linked steps, get the name from the source step
|
||||||
let step_name = if let Some(src) = source_idx {
|
let step_name = if let Some(src) = source_idx {
|
||||||
pattern.step(src).and_then(|s| s.name.as_ref())
|
pattern.step(src as usize).and_then(|s| s.name.as_ref())
|
||||||
} else {
|
} else {
|
||||||
step.and_then(|s| s.name.as_ref())
|
step.and_then(|s| s.name.as_ref())
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::time::{Duration, Instant};
|
|||||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
use ratatui::style::{Modifier, Style};
|
use ratatui::style::{Modifier, Style};
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::{Block, Borders, Cell, Paragraph, Row, Table};
|
use ratatui::widgets::{Block, Borders, Cell, Padding, Paragraph, Row, Table};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
@@ -28,15 +28,17 @@ fn adjust_spans_for_line(
|
|||||||
line_start: usize,
|
line_start: usize,
|
||||||
line_len: usize,
|
line_len: usize,
|
||||||
) -> Vec<SourceSpan> {
|
) -> Vec<SourceSpan> {
|
||||||
|
let ls = line_start as u32;
|
||||||
|
let ll = line_len as u32;
|
||||||
spans
|
spans
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|s| {
|
.filter_map(|s| {
|
||||||
if s.end <= line_start || s.start >= line_start + line_len {
|
if s.end <= ls || s.start >= ls + ll {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
Some(SourceSpan {
|
Some(SourceSpan {
|
||||||
start: s.start.max(line_start) - line_start,
|
start: s.start.max(ls) - ls,
|
||||||
end: (s.end.min(line_start + line_len)) - line_start,
|
end: (s.end.min(ls + ll)) - ls,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@@ -158,12 +160,8 @@ pub fn render(frame: &mut Frame, app: &App, link: &LinkState, snapshot: &Sequenc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header_height(width: u16) -> u16 {
|
fn header_height(_width: u16) -> u16 {
|
||||||
if width >= 80 {
|
3
|
||||||
1
|
|
||||||
} else {
|
|
||||||
2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_side_panel(frame: &mut Frame, app: &App, area: Rect) {
|
fn render_side_panel(frame: &mut Frame, app: &App, area: Rect) {
|
||||||
@@ -231,35 +229,18 @@ fn render_header(
|
|||||||
let bank = &app.project_state.project.banks[app.editor_ctx.bank];
|
let bank = &app.project_state.project.banks[app.editor_ctx.bank];
|
||||||
let pattern = &bank.patterns[app.editor_ctx.pattern];
|
let pattern = &bank.patterns[app.editor_ctx.pattern];
|
||||||
|
|
||||||
let (transport_area, live_area, tempo_area, bank_area, pattern_area, stats_area) =
|
let pad = Padding::vertical(1);
|
||||||
if area.height == 1 {
|
|
||||||
let [t, l, tp, b, p, s] = Layout::horizontal([
|
|
||||||
Constraint::Min(12),
|
|
||||||
Constraint::Length(9),
|
|
||||||
Constraint::Min(14),
|
|
||||||
Constraint::Fill(1),
|
|
||||||
Constraint::Fill(2),
|
|
||||||
Constraint::Min(20),
|
|
||||||
])
|
|
||||||
.areas(area);
|
|
||||||
(t, l, tp, b, p, s)
|
|
||||||
} else {
|
|
||||||
let [line1, line2] =
|
|
||||||
Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(area);
|
|
||||||
|
|
||||||
let [t, l, tp, s] = Layout::horizontal([
|
let [transport_area, live_area, tempo_area, bank_area, pattern_area, stats_area] =
|
||||||
Constraint::Min(12),
|
Layout::horizontal([
|
||||||
Constraint::Length(9),
|
Constraint::Min(12),
|
||||||
Constraint::Fill(1),
|
Constraint::Length(9),
|
||||||
Constraint::Min(20),
|
Constraint::Min(14),
|
||||||
])
|
Constraint::Fill(1),
|
||||||
.areas(line1);
|
Constraint::Fill(2),
|
||||||
|
Constraint::Min(20),
|
||||||
let [b, p] =
|
])
|
||||||
Layout::horizontal([Constraint::Fill(1), Constraint::Fill(2)]).areas(line2);
|
.areas(area);
|
||||||
|
|
||||||
(t, l, tp, b, p, s)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transport block
|
// Transport block
|
||||||
let (transport_bg, transport_text) = if app.playback.playing {
|
let (transport_bg, transport_text) = if app.playback.playing {
|
||||||
@@ -270,7 +251,7 @@ fn render_header(
|
|||||||
let transport_style = Style::new().bg(transport_bg).fg(theme.ui.text_primary);
|
let transport_style = Style::new().bg(transport_bg).fg(theme.ui.text_primary);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(transport_text)
|
Paragraph::new(transport_text)
|
||||||
.style(transport_style)
|
.block(Block::default().padding(pad).style(transport_style))
|
||||||
.alignment(Alignment::Center),
|
.alignment(Alignment::Center),
|
||||||
transport_area,
|
transport_area,
|
||||||
);
|
);
|
||||||
@@ -285,7 +266,7 @@ fn render_header(
|
|||||||
let fill_style = Style::new().bg(theme.status.fill_bg).fg(fill_fg);
|
let fill_style = Style::new().bg(theme.status.fill_bg).fg(fill_fg);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(if fill { "F" } else { "·" })
|
Paragraph::new(if fill { "F" } else { "·" })
|
||||||
.style(fill_style)
|
.block(Block::default().padding(pad).style(fill_style))
|
||||||
.alignment(Alignment::Center),
|
.alignment(Alignment::Center),
|
||||||
live_area,
|
live_area,
|
||||||
);
|
);
|
||||||
@@ -297,7 +278,7 @@ fn render_header(
|
|||||||
.add_modifier(Modifier::BOLD);
|
.add_modifier(Modifier::BOLD);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(format!(" {:.1} BPM ", link.tempo()))
|
Paragraph::new(format!(" {:.1} BPM ", link.tempo()))
|
||||||
.style(tempo_style)
|
.block(Block::default().padding(pad).style(tempo_style))
|
||||||
.alignment(Alignment::Center),
|
.alignment(Alignment::Center),
|
||||||
tempo_area,
|
tempo_area,
|
||||||
);
|
);
|
||||||
@@ -313,7 +294,7 @@ fn render_header(
|
|||||||
.fg(theme.ui.text_primary);
|
.fg(theme.ui.text_primary);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(bank_name)
|
Paragraph::new(bank_name)
|
||||||
.style(bank_style)
|
.block(Block::default().padding(pad).style(bank_style))
|
||||||
.alignment(Alignment::Center),
|
.alignment(Alignment::Center),
|
||||||
bank_area,
|
bank_area,
|
||||||
);
|
);
|
||||||
@@ -346,7 +327,7 @@ fn render_header(
|
|||||||
.fg(theme.ui.text_primary);
|
.fg(theme.ui.text_primary);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(pattern_text)
|
Paragraph::new(pattern_text)
|
||||||
.style(pattern_style)
|
.block(Block::default().padding(pad).style(pattern_style))
|
||||||
.alignment(Alignment::Center),
|
.alignment(Alignment::Center),
|
||||||
pattern_area,
|
pattern_area,
|
||||||
);
|
);
|
||||||
@@ -361,7 +342,7 @@ fn render_header(
|
|||||||
.fg(theme.header.stats_fg);
|
.fg(theme.header.stats_fg);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(stats_text)
|
Paragraph::new(stats_text)
|
||||||
.style(stats_style)
|
.block(Block::default().padding(pad).style(stats_style))
|
||||||
.alignment(Alignment::Right),
|
.alignment(Alignment::Right),
|
||||||
stats_area,
|
stats_area,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::harness::{default_ctx, expect_outputs, forth};
|
use crate::harness::{default_ctx, expect_outputs, forth};
|
||||||
use cagire::forth::{CcAccess, StepContext};
|
use cagire::forth::{CcAccess, StepContext};
|
||||||
use cagire::midi::CcMemory;
|
use cagire::midi::CcMemory;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use cagire::forth::Value;
|
use cagire::forth::Value;
|
||||||
@@ -52,7 +52,7 @@ fn test_ccval_reads_from_cc_memory() {
|
|||||||
|
|
||||||
let f = forth();
|
let f = forth();
|
||||||
let ctx = StepContext {
|
let ctx = StepContext {
|
||||||
cc_access: Some(Arc::new(cc_memory.clone()) as Arc<dyn CcAccess>),
|
cc_access: Some(&cc_memory as &dyn CcAccess),
|
||||||
..default_ctx()
|
..default_ctx()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ fn at_records_selected_spans() {
|
|||||||
assert_eq!(trace.selected_spans.len(), 6, "expected 6 selected spans (3 at + 3 sound)");
|
assert_eq!(trace.selected_spans.len(), 6, "expected 6 selected spans (3 at + 3 sound)");
|
||||||
|
|
||||||
// Verify at delta spans (even indices: 0, 2, 4)
|
// Verify at delta spans (even indices: 0, 2, 4)
|
||||||
assert_eq!(&script[trace.selected_spans[0].start..trace.selected_spans[0].end], "0");
|
assert_eq!(&script[trace.selected_spans[0].start as usize..trace.selected_spans[0].end as usize], "0");
|
||||||
assert_eq!(&script[trace.selected_spans[2].start..trace.selected_spans[2].end], "0.5");
|
assert_eq!(&script[trace.selected_spans[2].start as usize..trace.selected_spans[2].end as usize], "0.5");
|
||||||
assert_eq!(&script[trace.selected_spans[4].start..trace.selected_spans[4].end], "0.75");
|
assert_eq!(&script[trace.selected_spans[4].start as usize..trace.selected_spans[4].end as usize], "0.75");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user