Notes and intervals

This commit is contained in:
2026-01-22 01:38:55 +01:00
parent c73e25e207
commit 409e815414
9 changed files with 85965 additions and 0 deletions

75
tests/forth/intervals.rs Normal file
View File

@@ -0,0 +1,75 @@
use super::harness::{expect_int, expect_stack};
use cagire::model::forth::Value;
fn ints(vals: &[i64]) -> Vec<Value> {
vals.iter().map(|&v| Value::Int(v, None)).collect()
}
#[test]
fn interval_keeps_root() {
// Interval dups the root, then adds - so both values remain
expect_stack("c4 M3", &ints(&[60, 64]));
expect_stack("c4 P5", &ints(&[60, 67]));
}
#[test]
fn interval_stacking_builds_chord() {
// C major: root + M3 + m3 (stacked)
expect_stack("c4 M3 m3", &ints(&[60, 64, 67]));
// C minor: root + m3 + M3
expect_stack("c4 m3 M3", &ints(&[60, 63, 67]));
}
#[test]
fn interval_tritone() {
expect_stack("c4 tritone", &ints(&[60, 66]));
expect_stack("c4 aug4", &ints(&[60, 66]));
expect_stack("c4 dim5", &ints(&[60, 66]));
}
#[test]
fn interval_all_simple() {
expect_stack("c4 P1", &ints(&[60, 60])); // unison
expect_stack("c4 m2", &ints(&[60, 61]));
expect_stack("c4 M2", &ints(&[60, 62]));
expect_stack("c4 m3", &ints(&[60, 63]));
expect_stack("c4 M3", &ints(&[60, 64]));
expect_stack("c4 P4", &ints(&[60, 65]));
expect_stack("c4 P5", &ints(&[60, 67]));
expect_stack("c4 m6", &ints(&[60, 68]));
expect_stack("c4 M6", &ints(&[60, 69]));
expect_stack("c4 m7", &ints(&[60, 70]));
expect_stack("c4 M7", &ints(&[60, 71]));
expect_stack("c4 P8", &ints(&[60, 72]));
}
#[test]
fn interval_compound() {
expect_stack("c4 m9", &ints(&[60, 73]));
expect_stack("c4 M9", &ints(&[60, 74]));
expect_stack("c4 P15", &ints(&[60, 84]));
}
#[test]
fn interval_from_any_note() {
expect_stack("a4 m3", &ints(&[69, 72])); // A4 + m3 = C5
expect_stack("e4 P5", &ints(&[64, 71])); // E4 + P5 = B4
}
#[test]
fn interval_dominant_seventh() {
// C7: C E G Bb = root + M3 + m3 + m3
expect_stack("c4 M3 m3 m3", &ints(&[60, 64, 67, 70]));
}
#[test]
fn interval_major_seventh() {
// Cmaj7: C E G B = root + M3 + m3 + M3
expect_stack("c4 M3 m3 M3", &ints(&[60, 64, 67, 71]));
}
#[test]
fn interval_with_nip() {
// If you only want the new note, nip drops the one below top
expect_int("c4 M3 nip", 64);
}

62
tests/forth/notes.rs Normal file
View File

@@ -0,0 +1,62 @@
use super::harness::expect_int;
#[test]
fn note_c4_is_middle_c() {
expect_int("c4", 60);
}
#[test]
fn note_a4_is_440hz_reference() {
expect_int("a4", 69);
}
#[test]
fn note_c0() {
expect_int("c0", 12);
}
#[test]
fn note_sharps() {
expect_int("c#4", 61);
expect_int("cs4", 61);
expect_int("f#3", 54);
expect_int("fs3", 54);
}
#[test]
fn note_flats() {
expect_int("db4", 61);
expect_int("eb4", 63);
expect_int("ab4", 68);
expect_int("bb4", 70);
}
#[test]
fn note_all_naturals_octave4() {
expect_int("c4", 60);
expect_int("d4", 62);
expect_int("e4", 64);
expect_int("f4", 65);
expect_int("g4", 67);
expect_int("a4", 69);
expect_int("b4", 71);
}
#[test]
fn note_octave_range() {
expect_int("c0", 12);
expect_int("c1", 24);
expect_int("c2", 36);
expect_int("c3", 48);
expect_int("c5", 72);
expect_int("c6", 84);
expect_int("c7", 96);
expect_int("c8", 108);
expect_int("c9", 120);
}
#[test]
fn note_in_expression() {
expect_int("c4 12 +", 72); // C4 + octave = C5
expect_int("a4 c4 -", 9); // interval from C4 to A4
}

View File

@@ -15,6 +15,31 @@ fn dup_underflow() {
expect_error("dup", "stack underflow");
}
#[test]
fn dupn() {
expect_stack("2 4 dupn", &[int(2), int(2), int(2), int(2)]);
}
#[test]
fn dupn_one() {
expect_stack("5 1 dupn", &[int(5)]);
}
#[test]
fn dupn_zero() {
expect_stack("5 0 dupn", &[]);
}
#[test]
fn dupn_underflow() {
expect_error("3 dupn", "stack underflow");
}
#[test]
fn bang_alias() {
expect_stack("c4 3 !", &[int(60), int(60), int(60)]);
}
#[test]
fn drop() {
expect_stack("1 2 drop", &[int(1)]);