//! Musical scales and chord generation module //! //! This module provides definitions for various musical scales, chord progressions, //! and utilities for generating harmonically pleasing note sequences. use crate::midi_to_frequency; use rand::Rng; /// Musical scale types #[derive(Debug, Clone, Copy, PartialEq)] pub enum ScaleType { Major, Minor, Dorian, Phrygian, Lydian, Mixolydian, Aeolian, Locrian, Pentatonic, Blues, Chromatic, } /// A musical scale containing note intervals #[derive(Debug, Clone)] pub struct Scale { pub scale_type: ScaleType, pub root_note: u8, // MIDI note number pub intervals: Vec, // Semitone intervals from root } impl Scale { /// Create a new scale /// /// # Arguments /// * `scale_type` - Type of scale /// * `root_note` - Root note as MIDI number (0-127) pub fn new(scale_type: ScaleType, root_note: u8) -> Self { let intervals = match scale_type { ScaleType::Major => vec![0, 2, 4, 5, 7, 9, 11], ScaleType::Minor => vec![0, 2, 3, 5, 7, 8, 10], ScaleType::Dorian => vec![0, 2, 3, 5, 7, 9, 10], ScaleType::Phrygian => vec![0, 1, 3, 5, 7, 8, 10], ScaleType::Lydian => vec![0, 2, 4, 6, 7, 9, 11], ScaleType::Mixolydian => vec![0, 2, 4, 5, 7, 9, 10], ScaleType::Aeolian => vec![0, 2, 3, 5, 7, 8, 10], // Same as minor ScaleType::Locrian => vec![0, 1, 3, 5, 6, 8, 10], ScaleType::Pentatonic => vec![0, 2, 4, 7, 9], ScaleType::Blues => vec![0, 3, 5, 6, 7, 10], ScaleType::Chromatic => vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], }; Self { scale_type, root_note, intervals, } } /// Get all notes in the scale within a given octave range /// /// # Arguments /// * `octave_range` - Number of octaves to span (default 1) /// /// # Returns /// Vector of MIDI note numbers in the scale pub fn get_notes(&self, octave_range: u8) -> Vec { let mut notes = Vec::new(); for octave in 0..octave_range { for &interval in &self.intervals { let note = self.root_note + interval + (octave * 12); if note <= 127 { notes.push(note); } } } notes } /// Get notes in the scale as frequencies /// /// # Arguments /// * `octave_range` - Number of octaves to span /// /// # Returns /// Vector of frequencies in Hz pub fn get_frequencies(&self, octave_range: u8) -> Vec { self.get_notes(octave_range) .iter() .map(|¬e| midi_to_frequency(note)) .collect() } /// Get a specific degree of the scale /// /// # Arguments /// * `degree` - Scale degree (1-based, 1 = root) /// * `octave` - Octave number (0-based) /// /// # Returns /// MIDI note number, or None if degree is invalid pub fn get_degree(&self, degree: usize, octave: u8) -> Option { if degree == 0 || degree > self.intervals.len() { return None; } let interval = self.intervals[degree - 1]; let note = self.root_note + interval + (octave * 12); if note <= 127 { Some(note) } else { None } } /// Get a random note from the scale pub fn random_note(&self, octave_range: u8) -> u8 { let notes = self.get_notes(octave_range); let mut rng = rand::thread_rng(); notes[rng.gen_range(0..notes.len())] } /// Check if a MIDI note is in this scale pub fn contains_note(&self, note: u8) -> bool { let note_in_octave = note % 12; let root_in_octave = self.root_note % 12; self.intervals .iter() .any(|&interval| (root_in_octave + interval) % 12 == note_in_octave) } } /// Chord types #[derive(Debug, Clone, Copy, PartialEq)] pub enum ChordType { Major, Minor, Diminished, Augmented, Sus2, Sus4, Major7, Minor7, Dominant7, Major9, Minor9, } /// A musical chord #[derive(Debug, Clone)] pub struct Chord { pub chord_type: ChordType, pub root_note: u8, pub notes: Vec, } impl Chord { /// Create a new chord /// /// # Arguments /// * `chord_type` - Type of chord /// * `root_note` - Root note as MIDI number pub fn new(chord_type: ChordType, root_note: u8) -> Self { let intervals = match chord_type { ChordType::Major => vec![0, 4, 7], ChordType::Minor => vec![0, 3, 7], ChordType::Diminished => vec![0, 3, 6], ChordType::Augmented => vec![0, 4, 8], ChordType::Sus2 => vec![0, 2, 7], ChordType::Sus4 => vec![0, 5, 7], ChordType::Major7 => vec![0, 4, 7, 11], ChordType::Minor7 => vec![0, 3, 7, 10], ChordType::Dominant7 => vec![0, 4, 7, 10], ChordType::Major9 => vec![0, 4, 7, 11, 14], ChordType::Minor9 => vec![0, 3, 7, 10, 14], }; let notes = intervals .iter() .map(|&interval| root_note + interval) .filter(|¬e| note <= 127) .collect(); Self { chord_type, root_note, notes, } } /// Get chord notes as frequencies pub fn get_frequencies(&self) -> Vec { self.notes .iter() .map(|¬e| midi_to_frequency(note)) .collect() } /// Get an inversion of the chord /// /// # Arguments /// * `inversion` - Inversion number (0 = root position, 1 = first inversion, etc.) pub fn get_inversion(&self, inversion: usize) -> Vec { if inversion == 0 || inversion >= self.notes.len() { return self.notes.clone(); } let mut inverted = self.notes.clone(); // Move the bottom notes up an octave for i in 0..inversion { if inverted[i] + 12 <= 127 { inverted[i] += 12; } } // Sort the notes inverted.sort(); inverted } } /// Chord progression patterns #[derive(Debug, Clone)] pub struct ChordProgression { pub scale: Scale, pub progression: Vec, // Scale degrees (1-based) } impl ChordProgression { /// Create a new chord progression /// /// # Arguments /// * `scale` - The scale to build chords from /// * `progression` - Vector of scale degrees (1-based) pub fn new(scale: Scale, progression: Vec) -> Self { Self { scale, progression } } /// Get common chord progressions pub fn common_progressions() -> Vec> { vec![ vec![1, 4, 5, 1], // I-IV-V-I vec![1, 5, 6, 4], // I-V-vi-IV (pop progression) vec![6, 4, 1, 5], // vi-IV-I-V vec![1, 6, 4, 5], // I-vi-IV-V (50s progression) vec![2, 5, 1], // ii-V-I (jazz) vec![1, 7, 4, 1], // I-VII-IV-I vec![1, 3, 4, 1], // I-iii-IV-I ] } /// Generate chords for the progression /// /// # Arguments /// * `octave` - Base octave for the chords /// /// # Returns /// Vector of chords pub fn generate_chords(&self, octave: u8) -> Vec { self.progression .iter() .filter_map(|°ree| { self.scale.get_degree(degree, octave).map(|root_note| { // Determine chord type based on scale degree let chord_type = match self.scale.scale_type { ScaleType::Major => match degree { 1 | 4 | 5 => ChordType::Major, 2 | 3 | 6 => ChordType::Minor, 7 => ChordType::Diminished, _ => ChordType::Major, }, ScaleType::Minor => match degree { 1 | 4 | 5 => ChordType::Minor, 3 | 6 | 7 => ChordType::Major, 2 => ChordType::Diminished, _ => ChordType::Minor, }, _ => ChordType::Major, // Default for other scales }; Chord::new(chord_type, root_note) }) }) .collect() } } /// Melody generator using scales pub struct MelodyGenerator { pub scale: Scale, pub octave_range: u8, available_notes: Vec, } impl MelodyGenerator { /// Create a new melody generator pub fn new(scale: Scale, octave_range: u8) -> Self { let available_notes = scale.get_notes(octave_range); Self { scale, octave_range, available_notes, } } /// Generate a random melody /// /// # Arguments /// * `length` - Number of notes in the melody /// /// # Returns /// Vector of MIDI note numbers pub fn generate_random_melody(&self, length: usize) -> Vec { let mut rng = rand::thread_rng(); (0..length) .map(|_| self.available_notes[rng.gen_range(0..self.available_notes.len())]) .collect() } /// Generate a melody using step-wise motion /// /// # Arguments /// * `length` - Number of notes in the melody /// * `step_probability` - Probability of moving by step vs. leap (0.0 to 1.0) /// /// # Returns /// Vector of MIDI note numbers pub fn generate_stepwise_melody(&self, length: usize, step_probability: f32) -> Vec { if self.available_notes.is_empty() || length == 0 { return Vec::new(); } let mut rng = rand::thread_rng(); let mut melody = Vec::with_capacity(length); // Start with a random note let mut current_index = rng.gen_range(0..self.available_notes.len()); melody.push(self.available_notes[current_index]); for _ in 1..length { if rng.r#gen::() < step_probability { // Step-wise motion (move to adjacent note in scale) let direction = if rng.r#gen::() { 1 } else { -1 }; let new_index = (current_index as i32 + direction) .max(0) .min(self.available_notes.len() as i32 - 1) as usize; current_index = new_index; } else { // Leap (random note) current_index = rng.gen_range(0..self.available_notes.len()); } melody.push(self.available_notes[current_index]); } melody } /// Generate an arpeggio pattern /// /// # Arguments /// * `chord` - Chord to arpeggiate /// * `pattern_length` - Length of the arpeggio pattern /// /// # Returns /// Vector of MIDI note numbers pub fn generate_arpeggio(&self, chord: &Chord, pattern_length: usize) -> Vec { if chord.notes.is_empty() || pattern_length == 0 { return Vec::new(); } let mut arpeggio = Vec::with_capacity(pattern_length); let chord_notes = &chord.notes; for i in 0..pattern_length { let note_index = i % chord_notes.len(); arpeggio.push(chord_notes[note_index]); } arpeggio } } #[cfg(test)] mod tests { use super::*; #[test] fn test_scale_creation() { let scale = Scale::new(ScaleType::Major, 60); // C major assert_eq!(scale.intervals, vec![0, 2, 4, 5, 7, 9, 11]); assert_eq!(scale.root_note, 60); } #[test] fn test_scale_notes() { let scale = Scale::new(ScaleType::Major, 60); // C major let notes = scale.get_notes(1); assert_eq!(notes, vec![60, 62, 64, 65, 67, 69, 71]); // C D E F G A B } #[test] fn test_scale_degree() { let scale = Scale::new(ScaleType::Major, 60); // C major assert_eq!(scale.get_degree(1, 0), Some(60)); // C assert_eq!(scale.get_degree(5, 0), Some(67)); // G assert_eq!(scale.get_degree(8, 0), None); // Invalid degree } #[test] fn test_chord_creation() { let chord = Chord::new(ChordType::Major, 60); // C major assert_eq!(chord.notes, vec![60, 64, 67]); // C E G } #[test] fn test_chord_inversion() { let chord = Chord::new(ChordType::Major, 60); // C major let first_inversion = chord.get_inversion(1); assert_eq!(first_inversion, vec![64, 67, 72]); // E G C } #[test] fn test_chord_progression() { let scale = Scale::new(ScaleType::Major, 60); let progression = ChordProgression::new(scale, vec![1, 4, 5, 1]); let chords = progression.generate_chords(4); assert_eq!(chords.len(), 4); } #[test] fn test_melody_generator() { let scale = Scale::new(ScaleType::Pentatonic, 60); let generator = MelodyGenerator::new(scale, 2); let melody = generator.generate_random_melody(8); assert_eq!(melody.len(), 8); // All notes should be in the scale for ¬e in &melody { assert!(generator.available_notes.contains(¬e)); } } #[test] fn test_stepwise_melody() { let scale = Scale::new(ScaleType::Major, 60); let generator = MelodyGenerator::new(scale, 1); let melody = generator.generate_stepwise_melody(5, 1.0); // 100% stepwise assert_eq!(melody.len(), 5); } #[test] fn test_arpeggio_generation() { let scale = Scale::new(ScaleType::Major, 60); let generator = MelodyGenerator::new(scale, 1); let chord = Chord::new(ChordType::Major, 60); let arpeggio = generator.generate_arpeggio(&chord, 6); assert_eq!(arpeggio.len(), 6); // Should cycle through chord notes assert_eq!(arpeggio[0], chord.notes[0]); assert_eq!(arpeggio[3], chord.notes[0]); // After one full cycle } #[test] fn test_note_containment() { let scale = Scale::new(ScaleType::Major, 60); // C major assert!(scale.contains_note(60)); // C assert!(scale.contains_note(64)); // E assert!(!scale.contains_note(61)); // C# assert!(!scale.contains_note(63)); // D# } }