Feat: extend CI to cover desktop

This commit is contained in:
2026-01-30 21:19:48 +01:00
parent 6c9ec9a05f
commit bdba58312c
5 changed files with 61 additions and 17 deletions

View File

@@ -41,6 +41,7 @@ pub type Variables = Arc<Mutex<HashMap<String, Value>>>;
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 = Arc<Mutex<Vec<Value>>>;
pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(String, Value)]);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Value { pub enum Value {
@@ -140,8 +141,12 @@ impl CmdRegister {
&self.deltas &self.deltas
} }
pub(super) fn snapshot(&self) -> Option<(&Value, &[(String, Value)])> { pub(super) fn snapshot(&self) -> Option<CmdSnapshot<'_>> {
self.sound.as_ref().map(|s| (s, self.params.as_slice())) if self.sound.is_some() || !self.params.is_empty() {
Some((self.sound.as_ref(), self.params.as_slice()))
} else {
None
}
} }
pub(super) fn clear(&mut self) { pub(super) fn clear(&mut self) {

View File

@@ -147,9 +147,12 @@ impl Forth {
}; };
let emit_with_cycling = |cmd: &CmdRegister, emit_idx: usize, delta_secs: f64, outputs: &mut Vec<String>| -> Result<Option<Value>, String> { let emit_with_cycling = |cmd: &CmdRegister, emit_idx: usize, delta_secs: f64, outputs: &mut Vec<String>| -> Result<Option<Value>, String> {
let (sound_val, params) = cmd.snapshot().ok_or("no sound set")?; let (sound_opt, params) = cmd.snapshot().ok_or("nothing to emit")?;
let resolved_sound_val = resolve_cycling(sound_val, emit_idx); let resolved_sound_val = sound_opt.map(|sv| resolve_cycling(sv, emit_idx));
let sound = resolved_sound_val.as_str()?.to_string(); let sound_str = match &resolved_sound_val {
Some(v) => Some(v.as_str()?.to_string()),
None => None,
};
let resolved_params: Vec<(String, String)> = let resolved_params: Vec<(String, String)> =
params.iter().map(|(k, v)| { params.iter().map(|(k, v)| {
let resolved = resolve_cycling(v, emit_idx); let resolved = resolve_cycling(v, emit_idx);
@@ -162,8 +165,8 @@ impl Forth {
} }
(k.clone(), resolved.to_param_string()) (k.clone(), resolved.to_param_string())
}).collect(); }).collect();
emit_output(&sound, &resolved_params, ctx.step_duration(), delta_secs, outputs); emit_output(sound_str.as_deref(), &resolved_params, ctx.step_duration(), delta_secs, outputs);
Ok(Some(resolved_sound_val.into_owned())) Ok(resolved_sound_val.map(|v| v.into_owned()))
}; };
while pc < ops.len() { while pc < ops.len() {
@@ -796,25 +799,33 @@ fn is_tempo_scaled_param(name: &str) -> bool {
} }
fn emit_output( fn emit_output(
sound: &str, sound: Option<&str>,
params: &[(String, String)], params: &[(String, String)],
step_duration: f64, step_duration: f64,
nudge_secs: f64, nudge_secs: f64,
outputs: &mut Vec<String>, outputs: &mut Vec<String>,
) { ) {
let mut pairs = vec![("sound".into(), sound.to_string())]; let mut pairs: Vec<(String, String)> = if let Some(s) = sound {
vec![("sound".into(), s.to_string())]
} else {
vec![]
};
pairs.extend(params.iter().cloned()); pairs.extend(params.iter().cloned());
if nudge_secs > 0.0 { if nudge_secs > 0.0 {
pairs.push(("delta".into(), nudge_secs.to_string())); pairs.push(("delta".into(), nudge_secs.to_string()));
} }
if !pairs.iter().any(|(k, _)| k == "dur") { // Only add default dur if there's a sound (new voice)
if sound.is_some() && !pairs.iter().any(|(k, _)| k == "dur") {
pairs.push(("dur".into(), step_duration.to_string())); pairs.push(("dur".into(), step_duration.to_string()));
} }
if let Some(idx) = pairs.iter().position(|(k, _)| k == "delaytime") { // Only add default delaytime if there's a sound (new voice)
let ratio: f64 = pairs[idx].1.parse().unwrap_or(1.0); if sound.is_some() {
pairs[idx].1 = (ratio * step_duration).to_string(); if let Some(idx) = pairs.iter().position(|(k, _)| k == "delaytime") {
} else { let ratio: f64 = pairs[idx].1.parse().unwrap_or(1.0);
pairs.push(("delaytime".into(), step_duration.to_string())); pairs[idx].1 = (ratio * step_duration).to_string();
} else {
pairs.push(("delaytime".into(), step_duration.to_string()));
}
} }
for pair in &mut pairs { for pair in &mut pairs {
if is_tempo_scaled_param(&pair.0) { if is_tempo_scaled_param(&pair.0) {

View File

@@ -1,6 +1,9 @@
# Welcome to Cagire # Welcome to Cagire
Cagire is a terminal-based step sequencer for live coding music. Each step in a pattern contains a **Forth** script that produces sound and create events. It is made by BuboBubo (Raphaël Maurice Forment): [https://raphaelforment.fr](https://raphaelforment.fr). Cagire is open-source (AGPL-3.0 licensed) and available on GitHub : [https://github.com/BuboBubo/cagire](https://github.com/BuboBubo/cagire). This help view will teach you everything you need to know to start using Cagire and and to live code with it. Cagire is a terminal-based step sequencer for live coding music. Each step in a pattern contains a **Forth** script that produces sound and create events. It is made by BuboBubo (Raphaël Maurice Forment): [https://raphaelforment.fr](https://raphaelforment.fr). Cagire is open-source (AGPL-3.0 licensed) and available on GitHub : [https://github.com/BuboBubo/cagire](https://github.com/BuboBubo/cagire). This help view will teach you everything you need to know to start using Cagire and and to live code with it. To use Cagire, you will need to understand two things:
1) How the sequencer works: dealing with steps, patterns and banks.
2) How to write a script: how to make sound using code.
## Pages ## Pages

View File

@@ -245,6 +245,7 @@ fn preprocess_underscores(md: &str) -> String {
fn parse_markdown(md: &str) -> Vec<RLine<'static>> { fn parse_markdown(md: &str) -> Vec<RLine<'static>> {
let processed = preprocess_underscores(md); let processed = preprocess_underscores(md);
eprintln!("DEBUG parse_markdown: processed={:?}", &processed[..100.min(processed.len())]);
let text = minimad::Text::from(processed.as_str()); let text = minimad::Text::from(processed.as_str());
let mut lines = Vec::new(); let mut lines = Vec::new();
@@ -315,6 +316,11 @@ fn compound_to_spans(compound: Compound, base: Style, out: &mut Vec<Span<'static
let theme = theme::get(); let theme = theme::get();
let mut style = base; let mut style = base;
// DEBUG: Check if bold flag is being set
if compound.bold || compound.src.contains("**") {
eprintln!("compound: bold={} src={:?}", compound.bold, compound.src);
}
if compound.bold { if compound.bold {
style = style.add_modifier(Modifier::BOLD); style = style.add_modifier(Modifier::BOLD);
} }

View File

@@ -34,7 +34,7 @@ fn auto_delaytime() {
#[test] #[test]
fn emit_no_sound() { fn emit_no_sound() {
expect_error(".", "no sound set"); expect_error(".", "nothing to emit");
} }
#[test] #[test]
@@ -87,3 +87,22 @@ fn bank_param() {
assert!(outputs[0].contains("sound/loop")); assert!(outputs[0].contains("sound/loop"));
assert!(outputs[0].contains("bank/a")); assert!(outputs[0].contains("bank/a"));
} }
#[test]
fn param_only_emit() {
let outputs = expect_outputs(r#"0 voice 880 freq ."#, 1);
assert!(outputs[0].contains("voice/0"));
assert!(outputs[0].contains("freq/880"));
assert!(!outputs[0].contains("sound/"));
assert!(!outputs[0].contains("dur/"));
assert!(!outputs[0].contains("delaytime/"));
}
#[test]
fn param_only_multiple_params() {
let outputs = expect_outputs(r#"0 voice 440 freq 0.5 gain ."#, 1);
assert!(outputs[0].contains("voice/0"));
assert!(outputs[0].contains("freq/440"));
assert!(outputs[0].contains("gain/0.5"));
assert!(!outputs[0].contains("sound/"));
}