Initial commit

This commit is contained in:
2025-06-20 18:52:55 -06:00
commit 9e3bf41d61
18 changed files with 8429 additions and 0 deletions

476
src/scales.rs Normal file
View File

@ -0,0 +1,476 @@
//! 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<u8>, // 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<u8> {
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<f32> {
self.get_notes(octave_range)
.iter()
.map(|&note| 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<u8> {
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<u8>,
}
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(|&note| note <= 127)
.collect();
Self {
chord_type,
root_note,
notes,
}
}
/// Get chord notes as frequencies
pub fn get_frequencies(&self) -> Vec<f32> {
self.notes
.iter()
.map(|&note| 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<u8> {
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<usize>, // 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<usize>) -> Self {
Self { scale, progression }
}
/// Get common chord progressions
pub fn common_progressions() -> Vec<Vec<usize>> {
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<Chord> {
self.progression
.iter()
.filter_map(|&degree| {
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<u8>,
}
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<u8> {
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<u8> {
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::<f32>() < step_probability {
// Step-wise motion (move to adjacent note in scale)
let direction = if rng.r#gen::<bool>() { 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<u8> {
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 &note in &melody {
assert!(generator.available_notes.contains(&note));
}
}
#[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#
}
}