pub fn transpose(frequency: f32, semitones: i32) -> f32 {
frequency * 2.0f32.powf(semitones as f32 / 12.0)
}
pub struct ScalePattern {
pub intervals: &'static [i32],
}
impl ScalePattern {
pub const MAJOR: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 5, 7, 9, 11, 12],
};
pub const MINOR: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 5, 7, 8, 10, 12],
};
pub const HARMONIC_MINOR: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 5, 7, 8, 11, 12],
};
pub const MELODIC_MINOR: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 5, 7, 9, 11, 12],
};
pub const MAJOR_PENTATONIC: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 7, 9, 12],
};
pub const MINOR_PENTATONIC: ScalePattern = ScalePattern {
intervals: &[0, 3, 5, 7, 10, 12],
};
pub const BLUES: ScalePattern = ScalePattern {
intervals: &[0, 3, 5, 6, 7, 10, 12],
};
pub const CHROMATIC: ScalePattern = ScalePattern {
intervals: &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
};
pub const WHOLE_TONE: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 6, 8, 10, 12],
};
pub const DORIAN: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 5, 7, 9, 10, 12],
};
pub const PHRYGIAN: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 5, 7, 8, 10, 12],
};
pub const LYDIAN: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 6, 7, 9, 11, 12],
};
pub const MIXOLYDIAN: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 5, 7, 9, 10, 12],
};
pub const LOCRIAN: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 5, 6, 8, 10, 12],
};
pub const BEBOP_MAJOR: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 5, 7, 8, 9, 11, 12],
};
pub const BEBOP_DOMINANT: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 5, 7, 9, 10, 11, 12],
};
pub const BEBOP_MINOR: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 5, 7, 8, 9, 10, 12],
};
pub const ALTERED: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 4, 6, 8, 10, 12],
};
pub const DIMINISHED_HALF_WHOLE: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 4, 6, 7, 9, 10, 12],
};
pub const DIMINISHED_WHOLE_HALF: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 5, 6, 8, 9, 11, 12],
};
pub const HIRAJOSHI: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 7, 8, 12],
};
pub const IN_SEN: ScalePattern = ScalePattern {
intervals: &[0, 1, 5, 7, 10, 12],
};
pub const IWATO: ScalePattern = ScalePattern {
intervals: &[0, 1, 5, 6, 10, 12],
};
pub const YO: ScalePattern = ScalePattern {
intervals: &[0, 2, 5, 7, 9, 12],
};
pub const KUMOI: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 7, 9, 12],
};
pub const HIJAZ: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 5, 7, 8, 11, 12],
};
pub const DOUBLE_HARMONIC: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 5, 7, 8, 11, 12],
};
pub const PHRYGIAN_DOMINANT: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 5, 7, 8, 10, 12],
};
pub const PERSIAN: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 5, 6, 8, 11, 12],
};
pub const BHAIRAV: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 5, 7, 8, 11, 12],
};
pub const KAFI: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 5, 7, 9, 10, 12],
};
pub const BHAIRAVI: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 5, 7, 8, 10, 12],
};
pub const PURVI: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 6, 7, 8, 11, 12],
};
pub const MARVA: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 6, 7, 9, 11, 12],
};
pub const HUNGARIAN_MINOR: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 6, 7, 8, 11, 12],
};
pub const HUNGARIAN_MAJOR: ScalePattern = ScalePattern {
intervals: &[0, 3, 4, 6, 7, 9, 10, 12],
};
pub const GYPSY: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 5, 7, 8, 10, 12],
};
pub const SPANISH: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 5, 7, 8, 10, 12],
};
pub const FLAMENCO: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 4, 5, 7, 8, 10, 11, 12],
};
pub const ENIGMATIC: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 6, 8, 10, 11, 12],
};
pub const NEAPOLITAN_MAJOR: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 5, 7, 9, 11, 12],
};
pub const NEAPOLITAN_MINOR: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 5, 7, 8, 11, 12],
};
pub const PROMETHEUS: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 6, 9, 10, 12],
};
pub const TRITONE: ScalePattern = ScalePattern {
intervals: &[0, 1, 4, 6, 7, 10, 12],
};
pub const AUGMENTED: ScalePattern = ScalePattern {
intervals: &[0, 3, 4, 7, 8, 11, 12],
};
pub const EGYPTIAN: ScalePattern = ScalePattern {
intervals: &[0, 2, 5, 7, 10, 12],
};
pub const CHINESE: ScalePattern = ScalePattern {
intervals: &[0, 4, 6, 7, 11, 12],
};
pub const MONGOLIAN: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 7, 9, 12],
};
pub const LYDIAN_AUGMENTED: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 6, 8, 9, 11, 12],
};
pub const LYDIAN_DOMINANT: ScalePattern = ScalePattern {
intervals: &[0, 2, 4, 6, 7, 9, 10, 12],
};
pub const SUPER_LOCRIAN: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 4, 6, 8, 10, 12],
};
pub const ULTRA_LOCRIAN: ScalePattern = ScalePattern {
intervals: &[0, 1, 3, 4, 6, 8, 9, 12],
};
pub const HALF_DIMINISHED: ScalePattern = ScalePattern {
intervals: &[0, 2, 3, 5, 6, 8, 10, 12],
};
}
pub fn scale(root: f32, pattern: &ScalePattern) -> Vec<f32> {
pattern
.intervals
.iter()
.map(|&semitones| transpose(root, semitones))
.collect()
}
pub struct ChordPattern {
pub intervals: &'static [i32],
}
impl ChordPattern {
pub const MAJOR: ChordPattern = ChordPattern {
intervals: &[0, 4, 7],
};
pub const MINOR: ChordPattern = ChordPattern {
intervals: &[0, 3, 7],
};
pub const DIMINISHED: ChordPattern = ChordPattern {
intervals: &[0, 3, 6],
};
pub const AUGMENTED: ChordPattern = ChordPattern {
intervals: &[0, 4, 8],
};
pub const MAJOR7: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 11],
};
pub const MINOR7: ChordPattern = ChordPattern {
intervals: &[0, 3, 7, 10],
};
pub const DOMINANT7: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 10],
};
pub const DIMINISHED7: ChordPattern = ChordPattern {
intervals: &[0, 3, 6, 9],
};
pub const HALF_DIMINISHED7: ChordPattern = ChordPattern {
intervals: &[0, 3, 6, 10],
};
pub const SUS2: ChordPattern = ChordPattern {
intervals: &[0, 2, 7],
};
pub const SUS4: ChordPattern = ChordPattern {
intervals: &[0, 5, 7],
};
pub const ADD9: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 14],
};
pub const NINTH: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 10, 14],
};
pub const POWER: ChordPattern = ChordPattern { intervals: &[0, 7] };
pub const POWER_OCTAVE: ChordPattern = ChordPattern {
intervals: &[0, 7, 12],
};
pub const MAJOR6: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 9],
};
pub const MINOR6: ChordPattern = ChordPattern {
intervals: &[0, 3, 7, 9],
};
pub const DOMINANT7SUS4: ChordPattern = ChordPattern {
intervals: &[0, 5, 7, 10],
};
pub const MINOR_MAJOR7: ChordPattern = ChordPattern {
intervals: &[0, 3, 7, 11],
};
pub const ELEVENTH: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 10, 14, 17],
};
pub const THIRTEENTH: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 10, 14, 17, 21],
};
pub const DOMINANT7SHARP9: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 10, 15],
};
pub const DOMINANT7FLAT9: ChordPattern = ChordPattern {
intervals: &[0, 4, 7, 10, 13],
};
pub const DOMINANT7SHARP5: ChordPattern = ChordPattern {
intervals: &[0, 4, 8, 10],
};
pub const DOMINANT7FLAT5: ChordPattern = ChordPattern {
intervals: &[0, 4, 6, 10],
};
}
pub fn chord(root: f32, pattern: &ChordPattern) -> Vec<f32> {
pattern
.intervals
.iter()
.map(|&semitones| transpose(root, semitones))
.collect()
}
pub fn progression(
root: f32,
scale_pattern: &ScalePattern,
degrees: &[usize],
progression_type: ProgressionType,
) -> Vec<Vec<f32>> {
let scale_notes = scale(root, scale_pattern);
degrees
.iter()
.map(|°ree| {
let index = (degree - 1) % scale_notes.len();
let chord_root = scale_notes[index];
let chord_pattern = match progression_type {
ProgressionType::Triads => determine_triad(scale_pattern, degree),
ProgressionType::Sevenths => determine_seventh(scale_pattern, degree),
};
chord(chord_root, chord_pattern)
})
.collect()
}
pub enum ProgressionType {
Triads,
Sevenths,
}
fn determine_triad(scale_pattern: &ScalePattern, degree: usize) -> &'static ChordPattern {
if scale_pattern.intervals == ScalePattern::MAJOR.intervals {
match degree {
1 | 4 | 5 => &ChordPattern::MAJOR, 2 | 3 | 6 => &ChordPattern::MINOR, 7 => &ChordPattern::DIMINISHED, _ => &ChordPattern::MAJOR,
}
}
else if scale_pattern.intervals == ScalePattern::MINOR.intervals {
match degree {
3 | 6 | 7 => &ChordPattern::MAJOR, 1 | 4 | 5 => &ChordPattern::MINOR, 2 => &ChordPattern::DIMINISHED, _ => &ChordPattern::MINOR,
}
} else {
&ChordPattern::MAJOR
}
}
fn determine_seventh(scale_pattern: &ScalePattern, degree: usize) -> &'static ChordPattern {
if scale_pattern.intervals == ScalePattern::MAJOR.intervals {
match degree {
1 | 4 => &ChordPattern::MAJOR7, 5 => &ChordPattern::DOMINANT7, 2 | 3 | 6 => &ChordPattern::MINOR7, 7 => &ChordPattern::HALF_DIMINISHED7, _ => &ChordPattern::MAJOR7,
}
}
else if scale_pattern.intervals == ScalePattern::MINOR.intervals {
match degree {
1 | 4 | 5 => &ChordPattern::MINOR7, 3 | 6 => &ChordPattern::MAJOR7, 7 => &ChordPattern::DOMINANT7, 2 => &ChordPattern::HALF_DIMINISHED7, _ => &ChordPattern::MINOR7,
}
} else {
&ChordPattern::MAJOR7
}
}
pub fn transpose_sequence(notes: &[f32], semitones: i32) -> Vec<f32> {
notes
.iter()
.map(|¬e| transpose(note, semitones))
.collect()
}
pub fn scale_degree(root: f32, scale_pattern: &ScalePattern, degree: usize) -> f32 {
let scale_notes = scale(root, scale_pattern);
let index = (degree - 1) % scale_notes.len();
scale_notes[index]
}
pub fn chord_inversion(chord: &[f32], inversion: usize) -> Vec<f32> {
if chord.is_empty() {
return vec![];
}
let mut inverted = chord.to_vec();
let inv = inversion % chord.len();
for _ in 0..inv {
let note = inverted.remove(0);
inverted.push(note * 2.0); }
inverted
}
pub fn chord_over_bass(chord: &[f32], bass: f32) -> Vec<f32> {
let mut result = vec![bass];
for ¬e in chord {
let ratio = note / bass;
let octaves = ratio.log2();
let remainder = octaves - octaves.floor();
if remainder.abs() > 0.01 && (1.0 - remainder).abs() > 0.01 {
result.push(note);
}
}
result
}
pub fn voice_lead(from_chord: &[f32], to_chord: &[f32]) -> Vec<f32> {
if to_chord.is_empty() {
return vec![];
}
if from_chord.is_empty() {
return to_chord.to_vec();
}
let mut result = Vec::with_capacity(from_chord.len());
let mut used = vec![false; to_chord.len()];
for &from_note in from_chord {
let mut best_idx = 0;
let mut best_distance = f32::MAX;
for (i, &to_note) in to_chord.iter().enumerate() {
if used[i] {
continue;
}
for octave_shift in -2..=2 {
let transposed = to_note * 2.0f32.powi(octave_shift);
let distance = (from_note - transposed).abs();
if distance < best_distance {
best_distance = distance;
best_idx = i;
}
}
}
let chosen_note = to_chord[best_idx];
let mut best_octave_note = chosen_note;
let mut best_octave_distance = (from_note - chosen_note).abs();
for octave_shift in -2..=2 {
let transposed = chosen_note * 2.0f32.powi(octave_shift);
let distance = (from_note - transposed).abs();
if distance < best_octave_distance {
best_octave_distance = distance;
best_octave_note = transposed;
}
}
result.push(best_octave_note);
used[best_idx] = true;
}
result
}
pub fn close_voicing(chord: &[f32]) -> Vec<f32> {
if chord.is_empty() {
return vec![];
}
let mut sorted = chord.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let root = sorted[0];
let mut result = vec![root];
for ¬e in &sorted[1..] {
let mut adjusted = note;
while adjusted > root * 2.0 + 0.1 {
adjusted /= 2.0;
}
while adjusted < root - 0.1 {
adjusted *= 2.0;
}
adjusted = adjusted.max(root).min(root * 2.0);
result.push(adjusted);
}
result.sort_by(|a, b| a.partial_cmp(b).unwrap());
result.dedup_by(|a, b| (*a - *b).abs() < 0.1);
result
}
pub fn open_voicing(chord: &[f32]) -> Vec<f32> {
if chord.len() < 3 {
return chord.to_vec();
}
let mut result = chord.to_vec();
result.sort_by(|a, b| a.partial_cmp(b).unwrap());
let len = result.len();
if len >= 2 {
result[len - 2] /= 2.0;
}
result.sort_by(|a, b| a.partial_cmp(b).unwrap());
result
}
pub fn voice_leading_distance(from_chord: &[f32], to_chord: &[f32]) -> f32 {
if from_chord.len() != to_chord.len() {
return f32::MAX; }
let voiced = voice_lead(from_chord, to_chord);
from_chord
.iter()
.zip(voiced.iter())
.map(|(from, to)| (from - to).abs())
.sum()
}
pub struct Interval;
impl Interval {
pub const UNISON: i32 = 0;
pub const PERFECT_UNISON: i32 = 0;
pub const PERFECT_FOURTH: i32 = 5;
pub const PERFECT_FIFTH: i32 = 7;
pub const OCTAVE: i32 = 12;
pub const PERFECT_OCTAVE: i32 = 12;
pub const MAJOR_SECOND: i32 = 2;
pub const MAJOR_THIRD: i32 = 4;
pub const MAJOR_SIXTH: i32 = 9;
pub const MAJOR_SEVENTH: i32 = 11;
pub const MINOR_SECOND: i32 = 1;
pub const MINOR_THIRD: i32 = 3;
pub const MINOR_SIXTH: i32 = 8;
pub const MINOR_SEVENTH: i32 = 10;
pub const AUGMENTED_UNISON: i32 = 1;
pub const AUGMENTED_SECOND: i32 = 3;
pub const AUGMENTED_FOURTH: i32 = 6;
pub const AUGMENTED_FIFTH: i32 = 8;
pub const DIMINISHED_THIRD: i32 = 2;
pub const DIMINISHED_FOURTH: i32 = 4;
pub const DIMINISHED_FIFTH: i32 = 6;
pub const DIMINISHED_SEVENTH: i32 = 9;
pub const DIMINISHED_OCTAVE: i32 = 11;
pub const TRITONE: i32 = 6;
pub const HALF_STEP: i32 = 1;
pub const WHOLE_STEP: i32 = 2;
}
pub fn interval_between(from: f32, to: f32) -> i32 {
let ratio = to / from;
(12.0 * ratio.log2()).round() as i32
}
pub fn interval_name(semitones: i32) -> &'static str {
match semitones {
0 => "Perfect Unison",
1 => "Minor Second",
2 => "Major Second",
3 => "Minor Third",
4 => "Major Third",
5 => "Perfect Fourth",
6 => "Tritone",
7 => "Perfect Fifth",
8 => "Minor Sixth",
9 => "Major Sixth",
10 => "Minor Seventh",
11 => "Major Seventh",
12 => "Perfect Octave",
-1 => "Minor Second (down)",
-2 => "Major Second (down)",
-3 => "Minor Third (down)",
-4 => "Major Third (down)",
-5 => "Perfect Fourth (down)",
-6 => "Tritone (down)",
-7 => "Perfect Fifth (down)",
-8 => "Minor Sixth (down)",
-9 => "Major Sixth (down)",
-10 => "Minor Seventh (down)",
-11 => "Major Seventh (down)",
-12 => "Perfect Octave (down)",
_ if semitones > 12 => {
let remainder = semitones % 12;
if remainder == 0 {
return "Multiple Octaves";
}
"Compound Interval"
}
_ if semitones < -12 => {
let remainder = semitones.abs() % 12;
if remainder == 0 {
return "Multiple Octaves (down)";
}
"Compound Interval (down)"
}
_ => "Unknown Interval",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transpose() {
let c4 = 261.63;
let d4 = transpose(c4, 2);
assert!((d4 - 293.66).abs() < 0.1);
}
#[test]
fn test_scale_generation() {
let c4 = 261.63;
let c_major = scale(c4, &ScalePattern::MAJOR);
assert_eq!(c_major.len(), 8); }
#[test]
fn test_chord_generation() {
let c4 = 261.63;
let c_major_chord = chord(c4, &ChordPattern::MAJOR);
assert_eq!(c_major_chord.len(), 3); }
#[test]
fn test_all_chord_patterns_generate() {
let c4 = 261.63;
let chords = vec![
(&ChordPattern::MAJOR, 3),
(&ChordPattern::MINOR, 3),
(&ChordPattern::DIMINISHED, 3),
(&ChordPattern::AUGMENTED, 3),
(&ChordPattern::SUS2, 3),
(&ChordPattern::SUS4, 3),
(&ChordPattern::MAJOR7, 4),
(&ChordPattern::MINOR7, 4),
(&ChordPattern::DOMINANT7, 4),
(&ChordPattern::DIMINISHED7, 4),
(&ChordPattern::HALF_DIMINISHED7, 4),
(&ChordPattern::MAJOR6, 4),
(&ChordPattern::MINOR6, 4),
(&ChordPattern::DOMINANT7SUS4, 4),
(&ChordPattern::MINOR_MAJOR7, 4),
(&ChordPattern::ADD9, 4),
(&ChordPattern::NINTH, 5),
(&ChordPattern::ELEVENTH, 6),
(&ChordPattern::THIRTEENTH, 7),
(&ChordPattern::DOMINANT7SHARP9, 5),
(&ChordPattern::DOMINANT7FLAT9, 5),
(&ChordPattern::DOMINANT7SHARP5, 4),
(&ChordPattern::DOMINANT7FLAT5, 4),
(&ChordPattern::POWER, 2),
(&ChordPattern::POWER_OCTAVE, 3),
];
for (pattern, expected_len) in chords {
let result = chord(c4, pattern);
assert_eq!(
result.len(),
expected_len,
"Chord pattern should have {} notes",
expected_len
);
for &freq in &result {
assert!(freq > 0.0, "Chord frequencies should be positive");
}
assert!((result[0] - c4).abs() < 0.01, "First note should be root");
}
}
#[test]
fn test_jazz_chord_intervals() {
let c4 = 261.63;
let maj6 = chord(c4, &ChordPattern::MAJOR6);
assert_eq!(maj6.len(), 4);
let min6 = chord(c4, &ChordPattern::MINOR6);
assert_eq!(min6.len(), 4);
let min_maj7 = chord(c4, &ChordPattern::MINOR_MAJOR7);
assert_eq!(min_maj7.len(), 4);
let dom7sus4 = chord(c4, &ChordPattern::DOMINANT7SUS4);
assert_eq!(dom7sus4.len(), 4);
}
#[test]
fn test_altered_dominant_chords() {
let c4 = 261.63;
let dom7sharp9 = chord(c4, &ChordPattern::DOMINANT7SHARP9);
assert_eq!(dom7sharp9.len(), 5);
let dom7flat9 = chord(c4, &ChordPattern::DOMINANT7FLAT9);
assert_eq!(dom7flat9.len(), 5);
let dom7sharp5 = chord(c4, &ChordPattern::DOMINANT7SHARP5);
assert_eq!(dom7sharp5.len(), 4);
let dom7flat5 = chord(c4, &ChordPattern::DOMINANT7FLAT5);
assert_eq!(dom7flat5.len(), 4);
}
#[test]
fn test_extended_chords() {
let c4 = 261.63;
let eleventh = chord(c4, &ChordPattern::ELEVENTH);
assert_eq!(eleventh.len(), 6);
let thirteenth = chord(c4, &ChordPattern::THIRTEENTH);
assert_eq!(thirteenth.len(), 7);
for &freq in &eleventh[1..] {
assert!(freq > c4, "Extended notes should be higher than root");
}
}
#[test]
fn test_all_scales_generate() {
let c4 = 261.63;
let scales = vec![
(&ScalePattern::MAJOR, 8),
(&ScalePattern::MINOR, 8),
(&ScalePattern::HARMONIC_MINOR, 8),
(&ScalePattern::MELODIC_MINOR, 8),
(&ScalePattern::MAJOR_PENTATONIC, 6),
(&ScalePattern::MINOR_PENTATONIC, 6),
(&ScalePattern::BLUES, 7),
(&ScalePattern::CHROMATIC, 13),
(&ScalePattern::WHOLE_TONE, 7),
(&ScalePattern::DORIAN, 8),
(&ScalePattern::PHRYGIAN, 8),
(&ScalePattern::LYDIAN, 8),
(&ScalePattern::MIXOLYDIAN, 8),
(&ScalePattern::LOCRIAN, 8),
(&ScalePattern::BEBOP_MAJOR, 9),
(&ScalePattern::BEBOP_DOMINANT, 9),
(&ScalePattern::BEBOP_MINOR, 9),
(&ScalePattern::ALTERED, 8),
(&ScalePattern::DIMINISHED_HALF_WHOLE, 9),
(&ScalePattern::DIMINISHED_WHOLE_HALF, 9),
(&ScalePattern::HIRAJOSHI, 6),
(&ScalePattern::IN_SEN, 6),
(&ScalePattern::IWATO, 6),
(&ScalePattern::YO, 6),
(&ScalePattern::KUMOI, 6),
(&ScalePattern::HIJAZ, 8),
(&ScalePattern::DOUBLE_HARMONIC, 8),
(&ScalePattern::PHRYGIAN_DOMINANT, 8),
(&ScalePattern::PERSIAN, 8),
(&ScalePattern::BHAIRAV, 8),
(&ScalePattern::KAFI, 8),
(&ScalePattern::BHAIRAVI, 8),
(&ScalePattern::PURVI, 8),
(&ScalePattern::MARVA, 8),
(&ScalePattern::HUNGARIAN_MINOR, 8),
(&ScalePattern::HUNGARIAN_MAJOR, 8),
(&ScalePattern::GYPSY, 8),
(&ScalePattern::SPANISH, 8),
(&ScalePattern::FLAMENCO, 10),
(&ScalePattern::ENIGMATIC, 8),
(&ScalePattern::NEAPOLITAN_MAJOR, 8),
(&ScalePattern::NEAPOLITAN_MINOR, 8),
(&ScalePattern::PROMETHEUS, 7),
(&ScalePattern::TRITONE, 7),
(&ScalePattern::AUGMENTED, 7),
(&ScalePattern::EGYPTIAN, 6),
(&ScalePattern::CHINESE, 6),
(&ScalePattern::MONGOLIAN, 6),
(&ScalePattern::LYDIAN_AUGMENTED, 8),
(&ScalePattern::LYDIAN_DOMINANT, 8),
(&ScalePattern::SUPER_LOCRIAN, 8),
(&ScalePattern::ULTRA_LOCRIAN, 8),
(&ScalePattern::HALF_DIMINISHED, 8),
];
for (pattern, expected_len) in scales {
let generated = scale(c4, pattern);
assert_eq!(generated.len(), expected_len);
assert!((generated[0] - c4).abs() < 0.01);
assert!((generated[expected_len - 1] - c4 * 2.0).abs() < 1.0);
}
}
#[test]
fn test_japanese_scales_unique() {
let c4 = 261.63;
let hirajoshi = scale(c4, &ScalePattern::HIRAJOSHI);
let in_sen = scale(c4, &ScalePattern::IN_SEN);
let iwato = scale(c4, &ScalePattern::IWATO);
assert_ne!(hirajoshi, in_sen);
assert_ne!(in_sen, iwato);
assert_ne!(hirajoshi, iwato);
}
#[test]
fn test_middle_eastern_scales() {
let a4 = 440.0;
let hijaz = scale(a4, &ScalePattern::HIJAZ);
assert_eq!(hijaz.len(), 8);
let interval = hijaz[2] / hijaz[1];
assert!((interval - 2.0f32.powf(3.0 / 12.0)).abs() < 0.01);
}
#[test]
fn test_chord_inversion_basic() {
let c_major = vec![261.63, 329.63, 392.00];
let root = chord_inversion(&c_major, 0);
assert_eq!(root.len(), 3);
assert!((root[0] - 261.63).abs() < 0.1);
let first = chord_inversion(&c_major, 1);
assert_eq!(first.len(), 3);
assert!((first[0] - 329.63).abs() < 0.1); assert!((first[2] - 523.26).abs() < 1.0);
let second = chord_inversion(&c_major, 2);
assert_eq!(second.len(), 3);
assert!((second[0] - 392.00).abs() < 0.1); }
#[test]
fn test_chord_inversion_wrapping() {
let triad = vec![100.0, 125.0, 150.0];
let inv3 = chord_inversion(&triad, 3);
let inv0 = chord_inversion(&triad, 0);
assert_eq!(inv3, inv0);
let inv4 = chord_inversion(&triad, 4);
let inv1 = chord_inversion(&triad, 1);
assert_eq!(inv4, inv1);
}
#[test]
fn test_chord_over_bass() {
let c_major = vec![261.63, 329.63, 392.00];
let slash = chord_over_bass(&c_major, 164.81); assert_eq!(slash[0], 164.81); assert!(slash.contains(&261.63)); assert!(slash.contains(&392.00)); }
#[test]
fn test_voice_lead_smooth() {
let c_maj = vec![261.63, 329.63, 392.00]; let f_maj = vec![349.23, 440.00, 523.25];
let smooth = voice_lead(&c_maj, &f_maj);
assert_eq!(smooth.len(), 3);
assert!(smooth.iter().any(|&n| (n - 261.63).abs() < 10.0 || (n - 523.25).abs() < 10.0)); assert!(smooth.iter().any(|&n| (n - 349.23).abs() < 10.0)); assert!(smooth.iter().any(|&n| (n - 440.00).abs() < 10.0)); }
#[test]
fn test_voice_lead_empty_chords() {
let empty: Vec<f32> = vec![];
let chord = vec![261.63, 329.63, 392.00];
let result1 = voice_lead(&empty, &chord);
assert_eq!(result1, chord);
let result2 = voice_lead(&chord, &empty);
assert_eq!(result2.len(), 0);
}
#[test]
fn test_close_voicing() {
let wide = vec![130.81, 329.63, 523.25];
let close = close_voicing(&wide);
let lowest = close[0];
for ¬e in &close {
assert!(note >= lowest);
assert!(note <= lowest * 2.0);
}
assert_eq!(close.len(), 3);
}
#[test]
fn test_close_voicing_already_close() {
let already_close = vec![261.63, 329.63, 392.00];
let close = close_voicing(&already_close);
assert_eq!(close.len(), 3);
assert!((close[0] - 261.63).abs() < 0.1);
}
#[test]
fn test_open_voicing() {
let close = vec![261.63, 329.63, 392.00];
let open = open_voicing(&close);
assert_eq!(open.len(), 3);
assert!((open[0] - 164.81).abs() < 10.0 || (open[0] - 261.63).abs() < 10.0);
let span = open[2] / open[0];
assert!(span > 2.0); }
#[test]
fn test_voice_leading_distance() {
let c_maj = vec![261.63, 329.63, 392.00];
let c_maj_nearby = vec![261.63, 330.00, 393.00];
let distance_close = voice_leading_distance(&c_maj, &c_maj_nearby);
assert!(distance_close < 10.0);
let f_maj = vec![349.23, 440.00, 523.25];
let distance_far = voice_leading_distance(&c_maj, &f_maj);
assert!(distance_far > distance_close);
}
#[test]
fn test_voice_leading_distance_incompatible() {
let triad = vec![261.63, 329.63, 392.00];
let seventh = vec![261.63, 329.63, 392.00, 466.16];
let distance = voice_leading_distance(&triad, &seventh);
assert_eq!(distance, f32::MAX);
}
#[test]
fn test_interval_constants() {
assert_eq!(Interval::UNISON, 0);
assert_eq!(Interval::PERFECT_FOURTH, 5);
assert_eq!(Interval::PERFECT_FIFTH, 7);
assert_eq!(Interval::OCTAVE, 12);
assert_eq!(Interval::MAJOR_SECOND, 2);
assert_eq!(Interval::MAJOR_THIRD, 4);
assert_eq!(Interval::MAJOR_SIXTH, 9);
assert_eq!(Interval::MAJOR_SEVENTH, 11);
assert_eq!(Interval::MINOR_SECOND, 1);
assert_eq!(Interval::MINOR_THIRD, 3);
assert_eq!(Interval::MINOR_SIXTH, 8);
assert_eq!(Interval::MINOR_SEVENTH, 10);
assert_eq!(Interval::TRITONE, 6);
assert_eq!(Interval::AUGMENTED_FOURTH, 6);
assert_eq!(Interval::DIMINISHED_FIFTH, 6);
assert_eq!(Interval::HALF_STEP, 1);
assert_eq!(Interval::WHOLE_STEP, 2);
}
#[test]
fn test_interval_between() {
let c4 = 261.63;
let d4 = transpose(c4, 2);
let e4 = transpose(c4, 4);
let g4 = transpose(c4, 7);
let c5 = transpose(c4, 12);
assert_eq!(interval_between(c4, c4), 0); assert_eq!(interval_between(c4, d4), 2); assert_eq!(interval_between(c4, e4), 4); assert_eq!(interval_between(c4, g4), 7); assert_eq!(interval_between(c4, c5), 12);
assert_eq!(interval_between(g4, c4), -7); assert_eq!(interval_between(c5, c4), -12); }
#[test]
fn test_interval_between_with_constants() {
let c4 = 261.63;
let e4 = transpose(c4, Interval::MAJOR_THIRD);
let g4 = transpose(c4, Interval::PERFECT_FIFTH);
assert_eq!(interval_between(c4, e4), Interval::MAJOR_THIRD);
assert_eq!(interval_between(c4, g4), Interval::PERFECT_FIFTH);
assert_eq!(interval_between(e4, g4), Interval::MINOR_THIRD);
}
#[test]
fn test_interval_name() {
assert_eq!(interval_name(0), "Perfect Unison");
assert_eq!(interval_name(5), "Perfect Fourth");
assert_eq!(interval_name(7), "Perfect Fifth");
assert_eq!(interval_name(12), "Perfect Octave");
assert_eq!(interval_name(2), "Major Second");
assert_eq!(interval_name(4), "Major Third");
assert_eq!(interval_name(9), "Major Sixth");
assert_eq!(interval_name(11), "Major Seventh");
assert_eq!(interval_name(1), "Minor Second");
assert_eq!(interval_name(3), "Minor Third");
assert_eq!(interval_name(8), "Minor Sixth");
assert_eq!(interval_name(10), "Minor Seventh");
assert_eq!(interval_name(6), "Tritone");
assert_eq!(interval_name(-5), "Perfect Fourth (down)");
assert_eq!(interval_name(-7), "Perfect Fifth (down)");
assert_eq!(interval_name(24), "Multiple Octaves");
assert_eq!(interval_name(19), "Compound Interval"); assert_eq!(interval_name(-24), "Multiple Octaves (down)");
}
#[test]
fn test_transpose_with_interval_constants() {
let c4 = 261.63;
let e4 = transpose(c4, Interval::MAJOR_THIRD);
let g4 = transpose(c4, Interval::PERFECT_FIFTH);
let c5 = transpose(c4, Interval::OCTAVE);
assert!((e4 - transpose(c4, 4)).abs() < 0.01);
assert!((g4 - transpose(c4, 7)).abs() < 0.01);
assert!((c5 - transpose(c4, 12)).abs() < 0.01);
let g3 = transpose(c4, -Interval::PERFECT_FOURTH);
assert!((g3 - transpose(c4, -5)).abs() < 0.01);
}
#[test]
fn test_interval_semantic_chord_building() {
let root = 261.63;
let third = transpose(root, Interval::MAJOR_THIRD);
let fifth = transpose(root, Interval::PERFECT_FIFTH);
let manual_chord = vec![root, third, fifth];
let library_chord = chord(root, &ChordPattern::MAJOR);
assert_eq!(manual_chord.len(), library_chord.len());
for (manual, library) in manual_chord.iter().zip(library_chord.iter()) {
assert!((manual - library).abs() < 0.01);
}
}
#[test]
fn test_interval_semantic_harmony() {
let melody = vec![261.63, 293.66, 329.63];
let harmony: Vec<f32> = melody
.iter()
.map(|¬e| transpose(note, Interval::MAJOR_THIRD))
.collect();
for (melody_note, harmony_note) in melody.iter().zip(harmony.iter()) {
assert_eq!(interval_between(*melody_note, *harmony_note), Interval::MAJOR_THIRD);
}
}
#[test]
fn test_all_interval_constants_are_valid() {
let c4 = 261.63;
let intervals = vec![
Interval::UNISON,
Interval::MINOR_SECOND,
Interval::MAJOR_SECOND,
Interval::MINOR_THIRD,
Interval::MAJOR_THIRD,
Interval::PERFECT_FOURTH,
Interval::TRITONE,
Interval::PERFECT_FIFTH,
Interval::MINOR_SIXTH,
Interval::MAJOR_SIXTH,
Interval::MINOR_SEVENTH,
Interval::MAJOR_SEVENTH,
Interval::OCTAVE,
];
for interval in intervals {
let result = transpose(c4, interval);
assert!(result > 0.0);
assert!(result < 10000.0); }
}
}