Feat: new harmony / melodic words and demo

This commit is contained in:
2026-02-23 02:25:32 +01:00
parent d9e6505e07
commit e7137cc7ed
9 changed files with 624 additions and 9 deletions

View File

@@ -158,6 +158,68 @@ fn chord_dom7s5() {
expect_stack("c4 dom7s5", &ints(&[60, 64, 68, 70]));
}
// Power chord
#[test]
fn chord_power() {
expect_stack("c4 pwr", &ints(&[60, 67]));
}
// Suspended seventh
#[test]
fn chord_7sus4() {
expect_stack("c4 7sus4", &ints(&[60, 65, 67, 70]));
}
#[test]
fn chord_9sus4() {
expect_stack("c4 9sus4", &ints(&[60, 65, 67, 70, 74]));
}
// Augmented major
#[test]
fn chord_augmaj7() {
expect_stack("c4 augmaj7", &ints(&[60, 64, 68, 71]));
}
// 6/9 chords
#[test]
fn chord_maj69() {
expect_stack("c4 maj69", &ints(&[60, 64, 67, 69, 74]));
}
#[test]
fn chord_min69() {
expect_stack("c4 min69", &ints(&[60, 63, 67, 69, 74]));
}
// Extended - major
#[test]
fn chord_maj11() {
expect_stack("c4 maj11", &ints(&[60, 64, 67, 71, 74, 77]));
}
#[test]
fn chord_maj13() {
expect_stack("c4 maj13", &ints(&[60, 64, 67, 71, 74, 81]));
}
#[test]
fn chord_min13() {
expect_stack("c4 min13", &ints(&[60, 63, 67, 70, 74, 81]));
}
// Altered - lydian dominant
#[test]
fn chord_dom7s11() {
expect_stack("c4 dom7s11", &ints(&[60, 64, 67, 70, 78]));
}
// Different roots
#[test]

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

@@ -0,0 +1,193 @@
use cagire::forth::Value;
use super::harness::{expect_error, expect_int, expect_stack};
fn ints(vals: &[i64]) -> Vec<Value> {
vals.iter().map(|&v| Value::Int(v, None)).collect()
}
// Inversions
#[test]
fn invert_major_triad() {
expect_stack("c4 maj inv", &ints(&[64, 67, 72]));
}
#[test]
fn invert_twice() {
expect_stack("c4 maj inv inv", &ints(&[67, 72, 76]));
}
#[test]
fn down_invert_major_triad() {
expect_stack("c4 maj dinv", &ints(&[55, 60, 64]));
}
#[test]
fn down_invert_min7() {
expect_stack("c4 min7 dinv", &ints(&[58, 60, 63, 67]));
}
#[test]
fn invert_min7() {
expect_stack("c4 min7 inv", &ints(&[63, 67, 70, 72]));
}
// Voicings
#[test]
fn drop2_maj7() {
// c4 maj7 = [60, 64, 67, 71], 2nd from top = 67, drop to 55
expect_stack("c4 maj7 drop2", &ints(&[55, 60, 64, 71]));
}
#[test]
fn drop3_maj7() {
// c4 maj7 = [60, 64, 67, 71], 3rd from top = 64, drop to 52
expect_stack("c4 maj7 drop3", &ints(&[52, 60, 67, 71]));
}
// Key
#[test]
fn key_sets_tonal_center() {
expect_int("g3 key! 0 major", 55);
}
#[test]
fn key_with_degree() {
// G3=55, degree 4 of major = semitone 7, so 55+7=62
expect_int("g3 key! 4 major", 62);
}
#[test]
fn key_default_is_c4() {
expect_int("0 major", 60);
}
#[test]
fn key_a3_minor() {
expect_int("a3 key! 0 minor", 57);
}
// Diatonic triads
#[test]
fn diatonic_triad_degree_0() {
// C major: degrees 0,2,4 = C,E,G = 60,64,67
expect_stack("0 major triad", &ints(&[60, 64, 67]));
}
#[test]
fn diatonic_triad_degree_1() {
// D minor: degrees 1,3,5 = D,F,A = 62,65,69
expect_stack("1 major triad", &ints(&[62, 65, 69]));
}
#[test]
fn diatonic_triad_degree_3() {
// F major: degrees 3,5,7(=0+12) = F,A,C = 65,69,72
expect_stack("3 major triad", &ints(&[65, 69, 72]));
}
#[test]
fn diatonic_triad_degree_4() {
// G major: degrees 4,6,8(=1+12) = G,B,D = 67,71,74
expect_stack("4 major triad", &ints(&[67, 71, 74]));
}
// Diatonic sevenths
#[test]
fn diatonic_seventh_degree_0() {
// C major7: degrees 0,2,4,6 = C,E,G,B = 60,64,67,71
expect_stack("0 major seventh", &ints(&[60, 64, 67, 71]));
}
#[test]
fn diatonic_seventh_degree_1() {
// D minor7: degrees 1,3,5,7(=0+12) = D,F,A,C = 62,65,69,72
expect_stack("1 major seventh", &ints(&[62, 65, 69, 72]));
}
#[test]
fn diatonic_seventh_degree_4() {
// G dom7: degrees 4,6,8(=1+12),10(=3+12) = G,B,D,F = 67,71,74,77
expect_stack("4 major seventh", &ints(&[67, 71, 74, 77]));
}
// Combined
#[test]
fn key_with_diatonic_triad() {
// G3=55 key, degree 0 major triad = G,B,D = 55,59,62
expect_stack("g3 key! 0 major triad", &ints(&[55, 59, 62]));
}
#[test]
fn key_with_triad_inv() {
// C4 key, degree 0 minor triad = C,Eb,G = 60,63,67, then inv = Eb,G,C+12 = 63,67,72
expect_stack("0 minor triad inv", &ints(&[63, 67, 72]));
}
#[test]
fn key_degree_4_triad_in_g() {
// G3=55, degree 4 of major = notes at degrees 4,6,8(=1+12)
// major pattern: [0,2,4,5,7,9,11]
// degree 4 -> semitone 7, degree 6 -> semitone 11, degree 8(=1+12) -> 12+2=14
// 55+7=62, 55+11=66, 55+14=69 -> D, F#, A
expect_stack("g3 key! 4 major triad", &ints(&[62, 66, 69]));
}
// Backwards compatibility
#[test]
fn scale_degree_still_works() {
expect_int("0 major", 60);
expect_int("7 major", 72);
}
#[test]
fn chords_still_work() {
expect_stack("c4 maj", &ints(&[60, 64, 67]));
expect_stack("c4 min7", &ints(&[60, 63, 67, 70]));
}
// Transpose
#[test]
fn transpose_major_triad() {
expect_stack("c4 maj 3 tp", &ints(&[63, 67, 70]));
}
#[test]
fn transpose_down() {
expect_stack("c4 maj -2 tp", &ints(&[58, 62, 65]));
}
#[test]
fn transpose_zero() {
expect_stack("c4 maj 0 tp", &ints(&[60, 64, 67]));
}
#[test]
fn transpose_single_note() {
expect_int("60 7 tp", 67);
}
#[test]
fn transpose_octave() {
expect_stack("c4 min7 12 tp", &ints(&[72, 75, 79, 82]));
}
// Error cases
#[test]
fn triad_without_scale_errors() {
expect_error("0 triad", "unknown word");
}
#[test]
fn seventh_without_scale_errors() {
expect_error("0 seventh", "unknown word");
}