Reorganize repository
This commit is contained in:
@@ -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"
|
||||
|
||||
7
crates/forth/Cargo.toml
Normal file
7
crates/forth/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "cagire-forth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
@@ -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};
|
||||
@@ -76,4 +76,7 @@ pub enum Op {
|
||||
Echo,
|
||||
Necho,
|
||||
Apply,
|
||||
Ramp,
|
||||
Range,
|
||||
Noise,
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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 {
|
||||
8
crates/project/Cargo.toml
Normal file
8
crates/project/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "cagire-project"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
@@ -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 {
|
||||
7
crates/ratatui/Cargo.toml
Normal file
7
crates/ratatui/Cargo.toml
Normal 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
13
crates/ratatui/src/lib.rs
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
mod config;
|
||||
pub use cagire_forth as forth;
|
||||
pub mod model;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
mod app;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod engine;
|
||||
mod input;
|
||||
mod model;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")),
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -48,3 +48,6 @@ mod definitions;
|
||||
|
||||
#[path = "forth/list_words.rs"]
|
||||
mod list_words;
|
||||
|
||||
#[path = "forth/ramps.rs"]
|
||||
mod ramps;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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
193
tests/forth/ramps.rs
Normal 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");
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user