Reorganize repository

This commit is contained in:
2026-01-23 20:29:44 +01:00
parent e853e67492
commit 42ad77d9ae
44 changed files with 372 additions and 919964 deletions

View File

@@ -1,3 +1,6 @@
[workspace]
members = ["crates/forth", "crates/project", "crates/ratatui"]
[package]
name = "cagire"
version = "0.1.0"
@@ -12,13 +15,15 @@ name = "cagire"
path = "src/main.rs"
[dependencies]
cagire-forth = { path = "crates/forth" }
cagire-project = { path = "crates/project" }
cagire-ratatui = { path = "crates/ratatui" }
doux = { git = "https://github.com/Bubobubobubobubo/doux", features = ["native"] }
rusty_link = "0.4"
ratatui = "0.29"
crossterm = "0.28"
cpal = "0.15"
clap = { version = "4", features = ["derive"] }
rand = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

165719
arpeggio

File diff suppressed because it is too large Load Diff

42842
cool

File diff suppressed because it is too large Load Diff

7
crates/forth/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "cagire-forth"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8"

View File

@@ -4,8 +4,6 @@ mod types;
mod vm;
mod words;
pub use types::{Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Variables};
#[allow(unused_imports)]
pub use types::Value;
pub use types::{Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables};
pub use vm::Forth;
pub use words::{Word, WordCompile, WORDS};

View File

@@ -76,4 +76,7 @@ pub enum Op {
Echo,
Necho,
Apply,
Ramp,
Range,
Noise,
}

View File

@@ -881,6 +881,25 @@ impl Forth {
_ => return Err("expected quotation".into()),
}
}
Op::Ramp => {
let curve = stack.pop().ok_or("stack underflow")?.as_float()?;
let freq = stack.pop().ok_or("stack underflow")?.as_float()?;
let phase = (freq * ctx.beat).fract();
let phase = if phase < 0.0 { phase + 1.0 } else { phase };
let val = phase.powf(curve);
stack.push(Value::Float(val, None));
}
Op::Range => {
let max = stack.pop().ok_or("stack underflow")?.as_float()?;
let min = stack.pop().ok_or("stack underflow")?.as_float()?;
let val = stack.pop().ok_or("stack underflow")?.as_float()?;
stack.push(Value::Float(min + val * (max - min), None));
}
Op::Noise => {
let freq = stack.pop().ok_or("stack underflow")?.as_float()?;
let val = perlin_noise_1d(freq * ctx.beat);
stack.push(Value::Float(val, None));
}
}
pc += 1;
}
@@ -889,6 +908,23 @@ impl Forth {
}
}
fn perlin_grad(hash_input: i64) -> f64 {
let mut h = (hash_input as u64).wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
h ^= h >> 33;
h = h.wrapping_mul(0xff51afd7ed558ccd);
h ^= h >> 33;
if h & 1 == 0 { 1.0 } else { -1.0 }
}
fn perlin_noise_1d(x: f64) -> f64 {
let x0 = x.floor() as i64;
let t = x - x0 as f64;
let s = t * t * (3.0 - 2.0 * t);
let d0 = perlin_grad(x0) * t;
let d1 = perlin_grad(x0 + 1) * (t - 1.0);
(d0 + s * (d1 - d0)) * 0.5 + 0.5
}
fn binary_op<F>(stack: &mut Vec<Value>, f: F) -> Result<(), String>
where
F: Fn(f64, f64) -> f64,

View File

@@ -493,6 +493,49 @@ pub const WORDS: &[Word] = &[
example: "440 ftom => 69.0",
compile: Simple,
},
// Ramps
Word {
name: "ramp",
stack: "(freq curve -- val)",
desc: "Ramp [0,1]: fract(freq*beat)^curve",
example: "0.25 2.0 ramp",
compile: Simple,
},
Word {
name: "range",
stack: "(val min max -- scaled)",
desc: "Scale [0,1] to [min,max]",
example: "0.5 200 800 range => 500",
compile: Simple,
},
Word {
name: "linramp",
stack: "(freq -- val)",
desc: "Linear ramp (curve=1)",
example: "1.0 linramp",
compile: Simple,
},
Word {
name: "expramp",
stack: "(freq -- val)",
desc: "Exponential ramp (curve=3)",
example: "0.25 expramp",
compile: Simple,
},
Word {
name: "logramp",
stack: "(freq -- val)",
desc: "Logarithmic ramp (curve=0.3)",
example: "2.0 logramp",
compile: Simple,
},
Word {
name: "noise",
stack: "(freq -- val)",
desc: "Perlin noise [0,1] sampled at freq*beat",
example: "0.25 noise",
compile: Simple,
},
// Time
Word {
name: "at",
@@ -1253,6 +1296,27 @@ pub const WORDS: &[Word] = &[
example: "8 crush",
compile: Param,
},
Word {
name: "sub",
stack: "(f --)",
desc: "Set sub oscillator level",
example: "0.5 sub",
compile: Param,
},
Word {
name: "suboct",
stack: "(n --)",
desc: "Set sub oscillator octave",
example: "2 suboct",
compile: Param,
},
Word {
name: "subwave",
stack: "(n --)",
desc: "Set sub oscillator waveform",
example: "1 subwave",
compile: Param,
},
Word {
name: "fold",
stack: "(f --)",
@@ -1483,6 +1547,9 @@ pub(super) fn simple_op(name: &str) -> Option<Op> {
"echo" => Op::Echo,
"necho" => Op::Necho,
"apply" => Op::Apply,
"ramp" => Op::Ramp,
"range" => Op::Range,
"noise" => Op::Noise,
_ => return None,
})
}
@@ -1563,6 +1630,13 @@ fn parse_interval(name: &str) -> Option<i64> {
}
pub(super) fn compile_word(name: &str, span: Option<SourceSpan>, ops: &mut Vec<Op>, dict: &Dictionary) -> bool {
match name {
"linramp" => { ops.push(Op::PushFloat(1.0, span)); ops.push(Op::Ramp); return true; }
"expramp" => { ops.push(Op::PushFloat(3.0, span)); ops.push(Op::Ramp); return true; }
"logramp" => { ops.push(Op::PushFloat(0.3, span)); ops.push(Op::Ramp); return true; }
_ => {}
}
for word in WORDS {
if word.name == name {
match &word.compile {

View File

@@ -0,0 +1,8 @@
[package]
name = "cagire-project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

View File

@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use super::{Bank, Project};
use crate::project::{Bank, Project};
const VERSION: u8 = 1;

View File

@@ -1,4 +1,10 @@
mod file;
mod project;
pub const MAX_BANKS: usize = 16;
pub const MAX_PATTERNS: usize = 16;
pub const MAX_STEPS: usize = 128;
pub const DEFAULT_LENGTH: usize = 16;
pub use file::{load, save, FileError};
pub use project::{Bank, Pattern, PatternSpeed, Project, Step};

View File

@@ -2,7 +2,7 @@ use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::config::{DEFAULT_LENGTH, MAX_BANKS, MAX_PATTERNS, MAX_STEPS};
use crate::{DEFAULT_LENGTH, MAX_BANKS, MAX_PATTERNS, MAX_STEPS};
#[derive(Clone, Copy, Serialize, Deserialize, Default, PartialEq)]
pub enum PatternSpeed {

View File

@@ -0,0 +1,7 @@
[package]
name = "cagire-ratatui"
version = "0.1.0"
edition = "2021"
[dependencies]
ratatui = "0.29"

13
crates/ratatui/src/lib.rs Normal file
View File

@@ -0,0 +1,13 @@
mod confirm;
mod modal;
mod scope;
mod spectrum;
mod text_input;
mod vu_meter;
pub use confirm::ConfirmModal;
pub use modal::ModalFrame;
pub use scope::{Orientation, Scope};
pub use spectrum::Spectrum;
pub use text_input::TextInputModal;
pub use vu_meter::VuMeter;

42841
echo

File diff suppressed because it is too large Load Diff

165721
idm

File diff suppressed because it is too large Load Diff

165721
idm2

File diff suppressed because it is too large Load Diff

42841
notes

File diff suppressed because it is too large Load Diff

42842
ok

File diff suppressed because it is too large Load Diff

42842
okok

File diff suppressed because it is too large Load Diff

42841
something

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ use std::thread::{self, JoinHandle};
use std::time::Duration;
use super::LinkState;
use crate::config::{MAX_BANKS, MAX_PATTERNS};
use crate::model::{MAX_BANKS, MAX_PATTERNS};
use crate::model::{Dictionary, ExecutionTrace, Rng, ScriptEngine, StepContext, Variables};
use crate::state::LiveKeyState;

View File

@@ -1,2 +1,2 @@
mod config;
pub use cagire_forth as forth;
pub mod model;

View File

@@ -1,6 +1,5 @@
mod app;
mod commands;
mod config;
mod engine;
mod input;
mod model;

View File

@@ -1,8 +1,5 @@
mod file;
pub mod forth;
mod project;
mod script;
pub use file::{load, save};
pub use project::{Bank, Pattern, PatternSpeed, Project};
pub use cagire_forth::{Word, WordCompile, WORDS};
pub use cagire_project::{load, save, Bank, Pattern, PatternSpeed, Project, MAX_BANKS, MAX_PATTERNS};
pub use script::{Dictionary, ExecutionTrace, Rng, ScriptEngine, SourceSpan, StepContext, Variables};

View File

@@ -1,6 +1,6 @@
use super::forth::Forth;
use cagire_forth::Forth;
pub use super::forth::{Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Variables};
pub use cagire_forth::{Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Variables};
pub struct ScriptEngine {
forth: Forth,

View File

@@ -1,7 +1,7 @@
use std::collections::HashSet;
use std::path::PathBuf;
use crate::config::{MAX_BANKS, MAX_PATTERNS};
use crate::model::{MAX_BANKS, MAX_PATTERNS};
use crate::model::Project;
pub struct ProjectState {

View File

@@ -6,7 +6,7 @@ use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
use ratatui::Frame;
use crate::app::App;
use crate::model::forth::{Word, WordCompile, WORDS};
use crate::model::{Word, WordCompile, WORDS};
const STATIC_DOCS: &[(&str, &str)] = &[
("Keybindings", include_str!("../../docs/keybindings.md")),

View File

@@ -1,13 +1 @@
mod confirm;
mod modal;
mod scope;
mod spectrum;
mod text_input;
mod vu_meter;
pub use confirm::ConfirmModal;
pub use modal::ModalFrame;
pub use scope::{Orientation, Scope};
pub use spectrum::Spectrum;
pub use text_input::TextInputModal;
pub use vu_meter::VuMeter;
pub use cagire_ratatui::{ConfirmModal, ModalFrame, Orientation, Scope, Spectrum, TextInputModal, VuMeter};

165719
test

File diff suppressed because it is too large Load Diff

View File

@@ -48,3 +48,6 @@ mod definitions;
#[path = "forth/list_words.rs"]
mod list_words;
#[path = "forth/ramps.rs"]
mod ramps;

View File

@@ -46,7 +46,7 @@ fn float_literal() {
fn string_with_spaces() {
let f = run(r#""hello world" !x @x"#);
match stack_top(&f) {
cagire::model::forth::Value::Str(s, _) => assert_eq!(s, "hello world"),
cagire::forth::Value::Str(s, _) => assert_eq!(s, "hello world"),
other => panic!("expected string, got {:?}", other),
}
}

View File

@@ -1,6 +1,6 @@
use rand::rngs::StdRng;
use rand::SeedableRng;
use cagire::model::forth::{Dictionary, Forth, Rng, StepContext, Value, Variables};
use cagire::forth::{Dictionary, Forth, Rng, StepContext, Value, Variables};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};

View File

@@ -1,5 +1,5 @@
use super::harness::{expect_int, expect_stack};
use cagire::model::forth::Value;
use cagire::forth::Value;
fn ints(vals: &[i64]) -> Vec<Value> {
vals.iter().map(|&v| Value::Int(v, None)).collect()

193
tests/forth/ramps.rs Normal file
View File

@@ -0,0 +1,193 @@
use super::harness::*;
#[test]
fn ramp_at_beat_zero() {
let ctx = ctx_with(|c| c.beat = 0.0);
let f = forth();
f.evaluate("1.0 2.0 ramp", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 0.0).abs() < 1e-9, "expected 0.0, got {}", val);
}
#[test]
fn ramp_linear() {
let ctx = ctx_with(|c| c.beat = 0.5);
let f = forth();
f.evaluate("1.0 1.0 ramp", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val);
}
#[test]
fn ramp_quadratic() {
let ctx = ctx_with(|c| c.beat = 0.5);
let f = forth();
f.evaluate("1.0 2.0 ramp", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 0.25).abs() < 1e-9, "expected 0.25, got {}", val);
}
#[test]
fn ramp_sqrt() {
let ctx = ctx_with(|c| c.beat = 0.25);
let f = forth();
f.evaluate("1.0 0.5 ramp", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val);
}
#[test]
fn ramp_wraps() {
let ctx = ctx_with(|c| c.beat = 1.5);
let f = forth();
f.evaluate("1.0 1.0 ramp", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val);
}
#[test]
fn ramp_freq_half() {
let ctx = ctx_with(|c| c.beat = 1.0);
let f = forth();
f.evaluate("0.5 1.0 ramp", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val);
}
#[test]
fn ramp_underflow() {
expect_error("1.0 ramp", "stack underflow");
}
#[test]
fn range_mid() {
let ctx = default_ctx();
let f = forth();
f.evaluate("0.5 100.0 200.0 range", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 150.0).abs() < 1e-9, "expected 150.0, got {}", val);
}
#[test]
fn range_at_zero() {
let ctx = default_ctx();
let f = forth();
f.evaluate("0.0 100.0 200.0 range", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 100.0).abs() < 1e-9, "expected 100.0, got {}", val);
}
#[test]
fn range_at_one() {
let ctx = default_ctx();
let f = forth();
f.evaluate("1.0 100.0 200.0 range", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 200.0).abs() < 1e-9, "expected 200.0, got {}", val);
}
#[test]
fn range_underflow() {
expect_error("0.5 100.0 range", "stack underflow");
}
#[test]
fn linramp() {
let ctx = ctx_with(|c| c.beat = 0.5);
let f = forth();
f.evaluate("1.0 linramp", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val);
}
#[test]
fn expramp() {
let ctx = ctx_with(|c| c.beat = 0.5);
let f = forth();
f.evaluate("1.0 expramp", &ctx).unwrap();
let val = stack_float(&f);
let expected = 0.5_f64.powf(3.0);
assert!((val - expected).abs() < 1e-9, "expected {}, got {}", expected, val);
}
#[test]
fn logramp() {
let ctx = ctx_with(|c| c.beat = 0.5);
let f = forth();
f.evaluate("1.0 logramp", &ctx).unwrap();
let val = stack_float(&f);
let expected = 0.5_f64.powf(0.3);
assert!((val - expected).abs() < 1e-9, "expected {}, got {}", expected, val);
}
#[test]
fn ramp_with_range() {
let ctx = ctx_with(|c| c.beat = 0.5);
let f = forth();
f.evaluate("1.0 1.0 ramp 200.0 800.0 range", &ctx).unwrap();
let val = stack_float(&f);
assert!((val - 500.0).abs() < 1e-9, "expected 500.0, got {}", val);
}
#[test]
fn noise_deterministic() {
let ctx = ctx_with(|c| c.beat = 2.7);
let f = forth();
f.evaluate("1.0 noise", &ctx).unwrap();
let val1 = stack_float(&f);
f.evaluate("1.0 noise", &ctx).unwrap();
let val2 = stack_float(&f);
assert!((val1 - val2).abs() < 1e-9, "noise should be deterministic");
}
#[test]
fn noise_in_range() {
for i in 0..100 {
let ctx = ctx_with(|c| c.beat = i as f64 * 0.1);
let f = forth();
f.evaluate("1.0 noise", &ctx).unwrap();
let val = stack_float(&f);
assert!(val >= 0.0 && val <= 1.0, "noise out of range: {}", val);
}
}
#[test]
fn noise_varies() {
let ctx1 = ctx_with(|c| c.beat = 0.5);
let ctx2 = ctx_with(|c| c.beat = 1.5);
let f = forth();
f.evaluate("1.0 noise", &ctx1).unwrap();
let val1 = stack_float(&f);
f.evaluate("1.0 noise", &ctx2).unwrap();
let val2 = stack_float(&f);
assert!((val1 - val2).abs() > 1e-9, "noise should vary with beat");
}
#[test]
fn noise_smooth() {
let f = forth();
let mut prev = 0.0;
for i in 0..100 {
let ctx = ctx_with(|c| c.beat = i as f64 * 0.01);
f.evaluate("1.0 noise", &ctx).unwrap();
let val = stack_float(&f);
if i > 0 {
assert!((val - prev).abs() < 0.2, "noise not smooth: jump {} at step {}", (val - prev).abs(), i);
}
prev = val;
}
}
#[test]
fn noise_with_range() {
let ctx = ctx_with(|c| c.beat = 1.3);
let f = forth();
f.evaluate("1.0 noise 200.0 800.0 range", &ctx).unwrap();
let val = stack_float(&f);
assert!(val >= 200.0 && val <= 800.0, "noise+range out of bounds: {}", val);
}
#[test]
fn noise_underflow() {
expect_error("noise", "stack underflow");
}

View File

@@ -1,5 +1,5 @@
use super::harness::*;
use cagire::model::forth::Value;
use cagire::forth::Value;
fn int(n: i64) -> Value {
Value::Int(n, None)