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

730
src/patterns.rs Normal file
View File

@ -0,0 +1,730 @@
//! Rhythmic and melodic pattern generation module
//!
//! This module provides tools for generating and manipulating musical patterns,
//! including rhythm patterns, melodic sequences, and pattern transformations.
use crate::scales::Scale;
use rand::Rng;
/// A rhythmic pattern representation
#[derive(Debug, Clone)]
pub struct RhythmPattern {
pub steps: Vec<RhythmStep>,
pub steps_per_beat: usize,
pub length: usize, // Total number of steps
}
/// A single step in a rhythm pattern
#[derive(Debug, Clone, Copy)]
pub struct RhythmStep {
pub active: bool,
pub velocity: f32,
pub accent: bool,
}
impl RhythmStep {
pub fn new(active: bool, velocity: f32, accent: bool) -> Self {
Self {
active,
velocity: velocity.clamp(0.0, 1.0),
accent,
}
}
pub fn hit(velocity: f32) -> Self {
Self::new(true, velocity, false)
}
pub fn accent_hit(velocity: f32) -> Self {
Self::new(true, velocity, true)
}
pub fn rest() -> Self {
Self::new(false, 0.0, false)
}
}
impl RhythmPattern {
/// Create a new rhythm pattern
///
/// # Arguments
/// * `length` - Total number of steps in the pattern
/// * `steps_per_beat` - Number of steps per beat (e.g., 4 for sixteenth notes)
pub fn new(length: usize, steps_per_beat: usize) -> Self {
let steps = vec![RhythmStep::rest(); length];
Self {
steps,
steps_per_beat,
length,
}
}
/// Create a basic kick drum pattern (4/4 time)
pub fn kick_pattern() -> Self {
let mut pattern = Self::new(16, 4);
pattern.steps[0] = RhythmStep::accent_hit(1.0); // Beat 1
pattern.steps[4] = RhythmStep::hit(0.8); // Beat 2
pattern.steps[8] = RhythmStep::accent_hit(1.0); // Beat 3
pattern.steps[12] = RhythmStep::hit(0.8); // Beat 4
pattern
}
/// Create a basic snare pattern
pub fn snare_pattern() -> Self {
let mut pattern = Self::new(16, 4);
pattern.steps[4] = RhythmStep::accent_hit(0.9); // Beat 2
pattern.steps[12] = RhythmStep::accent_hit(0.9); // Beat 4
pattern
}
/// Create a hi-hat pattern
pub fn hihat_pattern() -> Self {
let mut pattern = Self::new(16, 4);
for i in 0..16 {
if i % 2 == 0 {
pattern.steps[i] = RhythmStep::hit(0.6);
} else {
pattern.steps[i] = RhythmStep::hit(0.4);
}
}
pattern
}
/// Create a Euclidean rhythm pattern
///
/// # Arguments
/// * `hits` - Number of hits to distribute
/// * `steps` - Total number of steps
pub fn euclidean(hits: usize, steps: usize) -> Self {
let mut pattern = Self::new(steps, 4);
if hits == 0 {
return pattern;
}
let mut remainders = vec![hits];
let mut counts = vec![steps / hits];
let mut divisor = steps % hits;
let mut level = 0;
while divisor != 0 && level < 16 {
let temp_divisor = remainders[level] % divisor;
let temp_count = remainders[level] / divisor;
counts.push(temp_count);
remainders.push(divisor);
remainders[level] = temp_divisor;
divisor = temp_divisor;
level += 1;
}
// Build the pattern
let mut result = Vec::new();
for i in 0..counts.len() {
for _ in 0..remainders[i] {
if i % 2 == 0 {
result.push(true);
} else {
result.push(false);
}
for _ in 1..counts[i] {
result.push(false);
}
}
}
// Fill remaining steps
while result.len() < steps {
result.push(false);
}
for (i, &active) in result.iter().enumerate() {
if i < pattern.steps.len() {
pattern.steps[i] = if active {
RhythmStep::hit(0.8)
} else {
RhythmStep::rest()
};
}
}
pattern
}
/// Set a step in the pattern
pub fn set_step(&mut self, index: usize, step: RhythmStep) {
if index < self.steps.len() {
self.steps[index] = step;
}
}
/// Get a step from the pattern
pub fn get_step(&self, index: usize) -> Option<RhythmStep> {
self.steps.get(index).copied()
}
/// Rotate the pattern by a number of steps
pub fn rotate(&mut self, steps: i32) {
if self.steps.is_empty() {
return;
}
let len = self.steps.len() as i32;
let effective_steps = ((steps % len) + len) % len;
self.steps.rotate_right(effective_steps as usize);
}
/// Reverse the pattern
pub fn reverse(&mut self) {
self.steps.reverse();
}
/// Add random variations to the pattern
pub fn add_variation(&mut self, probability: f32) {
let mut rng = rand::thread_rng();
for step in &mut self.steps {
if rng.r#gen::<f32>() < probability {
if step.active {
// Randomly remove hits
if rng.r#gen::<f32>() < 0.3 {
step.active = false;
} else {
// Vary velocity
step.velocity = (step.velocity + rng.gen_range(-0.2..0.2)).clamp(0.0, 1.0);
}
} else {
// Randomly add hits
if rng.r#gen::<f32>() < 0.1 {
step.active = true;
step.velocity = 0.3 + rng.r#gen::<f32>() * 0.4;
}
}
}
}
}
/// Get timing information for active steps
///
/// # Arguments
/// * `beat_duration` - Duration of one beat in samples
///
/// # Returns
/// Vector of (step_index, timing_in_samples, velocity)
pub fn get_timings(&self, beat_duration: usize) -> Vec<(usize, usize, f32)> {
let mut timings = Vec::new();
let step_duration = beat_duration / self.steps_per_beat;
for (i, step) in self.steps.iter().enumerate() {
if step.active {
let timing = (i * step_duration) / self.steps_per_beat;
timings.push((i, timing, step.velocity));
}
}
timings
}
/// Combine this pattern with another pattern
pub fn combine(&self, other: &RhythmPattern, mode: CombineMode) -> RhythmPattern {
let max_len = self.steps.len().max(other.steps.len());
let mut combined = RhythmPattern::new(max_len, self.steps_per_beat);
for i in 0..max_len {
let self_step = self.steps.get(i).copied().unwrap_or(RhythmStep::rest());
let other_step = other.steps.get(i).copied().unwrap_or(RhythmStep::rest());
combined.steps[i] = match mode {
CombineMode::Or => {
if self_step.active || other_step.active {
RhythmStep::hit(self_step.velocity.max(other_step.velocity))
} else {
RhythmStep::rest()
}
}
CombineMode::And => {
if self_step.active && other_step.active {
RhythmStep::hit((self_step.velocity + other_step.velocity) / 2.0)
} else {
RhythmStep::rest()
}
}
CombineMode::Xor => {
if self_step.active ^ other_step.active {
RhythmStep::hit(self_step.velocity.max(other_step.velocity))
} else {
RhythmStep::rest()
}
}
};
}
combined
}
}
/// Modes for combining rhythm patterns
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CombineMode {
Or, // Either pattern has a hit
And, // Both patterns have a hit
Xor, // Only one pattern has a hit
}
/// A melodic pattern representation
#[derive(Debug, Clone)]
pub struct MelodicPattern {
pub notes: Vec<PatternNote>,
pub scale: Scale,
pub octave_range: u8,
}
/// A note in a melodic pattern
#[derive(Debug, Clone, Copy)]
pub struct PatternNote {
pub scale_degree: usize, // 1-based scale degree
pub octave_offset: i8, // Octave offset from base
pub duration: f32, // In beats
pub velocity: f32,
pub is_rest: bool,
}
impl PatternNote {
pub fn new(scale_degree: usize, octave_offset: i8, duration: f32, velocity: f32) -> Self {
Self {
scale_degree,
octave_offset,
duration,
velocity: velocity.clamp(0.0, 1.0),
is_rest: false,
}
}
pub fn rest(duration: f32) -> Self {
Self {
scale_degree: 1,
octave_offset: 0,
duration,
velocity: 0.0,
is_rest: true,
}
}
}
impl MelodicPattern {
/// Create a new melodic pattern
pub fn new(scale: Scale, octave_range: u8) -> Self {
Self {
notes: Vec::new(),
scale,
octave_range,
}
}
/// Add a note to the pattern
pub fn add_note(&mut self, note: PatternNote) {
self.notes.push(note);
}
/// Generate a random melodic pattern
///
/// # Arguments
/// * `length` - Number of notes in the pattern
/// * `note_durations` - Possible note durations
pub fn generate_random(&mut self, length: usize, note_durations: &[f32]) {
let mut rng = rand::thread_rng();
self.notes.clear();
let scale_degrees = self.scale.intervals.len();
for _ in 0..length {
if rng.r#gen::<f32>() < 0.1 {
// 10% chance of rest
let duration = note_durations[rng.gen_range(0..note_durations.len())];
self.notes.push(PatternNote::rest(duration));
} else {
let degree = rng.gen_range(1..=scale_degrees);
let octave_offset = rng.gen_range(-1..=1);
let duration = note_durations[rng.gen_range(0..note_durations.len())];
let velocity = 0.5 + rng.r#gen::<f32>() * 0.4;
self.notes
.push(PatternNote::new(degree, octave_offset, duration, velocity));
}
}
}
/// Generate an ascending scale pattern
pub fn generate_ascending(&mut self, octaves: u8) {
self.notes.clear();
for octave in 0..octaves {
for (i, _) in self.scale.intervals.iter().enumerate() {
let degree = i + 1;
let octave_offset = octave as i8;
self.notes
.push(PatternNote::new(degree, octave_offset, 0.5, 0.8));
}
}
}
/// Generate a descending scale pattern
pub fn generate_descending(&mut self, octaves: u8) {
self.notes.clear();
for octave in (0..octaves).rev() {
for (i, _) in self.scale.intervals.iter().enumerate().rev() {
let degree = i + 1;
let octave_offset = octave as i8;
self.notes
.push(PatternNote::new(degree, octave_offset, 0.5, 0.8));
}
}
}
/// Generate an arpeggio pattern
///
/// # Arguments
/// * `chord_degrees` - Scale degrees that form the chord (e.g., [1, 3, 5])
/// * `direction` - 1 for ascending, -1 for descending
pub fn generate_arpeggio(
&mut self,
chord_degrees: &[usize],
direction: i8,
repetitions: usize,
) {
self.notes.clear();
for _ in 0..repetitions {
let degrees = if direction > 0 {
chord_degrees.to_vec()
} else {
let mut rev = chord_degrees.to_vec();
rev.reverse();
rev
};
for &degree in &degrees {
self.notes.push(PatternNote::new(degree, 0, 0.25, 0.7));
}
}
}
/// Apply swing timing to the pattern
///
/// # Arguments
/// * `swing_ratio` - Swing ratio (0.5 = straight, 0.67 = heavy swing)
pub fn apply_swing(&mut self, swing_ratio: f32) {
let swing_ratio = swing_ratio.clamp(0.5, 0.8);
for (i, note) in self.notes.iter_mut().enumerate() {
if i % 2 == 1 && !note.is_rest {
// Apply swing to off-beat notes
note.duration *= swing_ratio;
}
}
}
/// Transpose the pattern by semitones
pub fn transpose(&mut self, semitones: i8) {
// Note: This would require adjusting the scale root
// For now, we'll adjust octave offsets when possible
let octave_adjustment = semitones / 12;
for note in &mut self.notes {
note.octave_offset += octave_adjustment;
}
}
/// Get the MIDI notes for this pattern
///
/// # Arguments
/// * `base_octave` - Base octave for the pattern
///
/// # Returns
/// Vector of (midi_note, start_time, duration, velocity)
pub fn get_midi_notes(&self, base_octave: u8) -> Vec<(u8, f32, f32, f32)> {
let mut midi_notes = Vec::new();
let mut current_time = 0.0;
for note in &self.notes {
if !note.is_rest {
if let Some(midi_note) = self.scale.get_degree(
note.scale_degree,
(base_octave as i8 + note.octave_offset).max(0) as u8,
) {
midi_notes.push((midi_note, current_time, note.duration, note.velocity));
}
}
current_time += note.duration;
}
midi_notes
}
/// Get the total duration of the pattern
pub fn total_duration(&self) -> f32 {
self.notes.iter().map(|n| n.duration).sum()
}
/// Repeat the pattern a number of times
pub fn repeat(&mut self, times: usize) {
if times <= 1 {
return;
}
let original_notes = self.notes.clone();
self.notes.clear();
for _ in 0..times {
self.notes.extend(original_notes.iter().cloned());
}
}
}
/// Pattern transformation utilities
pub struct PatternTransforms;
impl PatternTransforms {
/// Create a polyrhythm by combining patterns of different lengths
pub fn create_polyrhythm(patterns: Vec<RhythmPattern>) -> RhythmPattern {
if patterns.is_empty() {
return RhythmPattern::new(16, 4);
}
// Find the least common multiple of all pattern lengths
let lcm = patterns.iter().fold(1, |acc, p| lcm(acc, p.length));
let steps_per_beat = patterns[0].steps_per_beat;
let mut result = RhythmPattern::new(lcm, steps_per_beat);
for (i, step) in result.steps.iter_mut().enumerate() {
let mut combined_velocity = 0.0;
let mut has_hit = false;
for pattern in &patterns {
let pattern_index = i % pattern.length;
if pattern.steps[pattern_index].active {
has_hit = true;
combined_velocity += pattern.steps[pattern_index].velocity;
}
}
if has_hit {
*step = RhythmStep::hit(combined_velocity / patterns.len() as f32);
}
}
result
}
/// Create a rhythmic canon (same pattern with time offsets)
pub fn create_canon(
pattern: &RhythmPattern,
tracks: usize,
offset_steps: usize,
) -> Vec<RhythmPattern> {
let mut canons = Vec::new();
for track in 0..tracks {
let mut canon_pattern = pattern.clone();
let offset = (track * offset_steps) % pattern.length;
canon_pattern.rotate(offset as i32);
canons.push(canon_pattern);
}
canons
}
/// Generate variations of a melodic pattern
pub fn generate_melodic_variations(
pattern: &MelodicPattern,
variations: usize,
) -> Vec<MelodicPattern> {
let mut variations_vec = Vec::new();
let mut rng = rand::thread_rng();
for _ in 0..variations {
let mut variation = pattern.clone();
// Apply random transformations
match rng.gen_range(0..4) {
0 => {
// Rhythmic variation
for note in &mut variation.notes {
if rng.r#gen::<f32>() < 0.3 {
note.duration *= rng.gen_range(0.5..2.0);
}
}
}
1 => {
// Octave displacement
for note in &mut variation.notes {
if rng.r#gen::<f32>() < 0.2 {
note.octave_offset += if rng.r#gen::<bool>() { 1 } else { -1 };
}
}
}
2 => {
// Add ornaments (grace notes)
let mut ornamented = Vec::new();
for note in &variation.notes {
if rng.r#gen::<f32>() < 0.15 && !note.is_rest {
// Add grace note
let grace_degree = if note.scale_degree > 1 {
note.scale_degree - 1
} else {
note.scale_degree + 1
};
ornamented.push(PatternNote::new(
grace_degree,
note.octave_offset,
0.1,
note.velocity * 0.7,
));
}
ornamented.push(*note);
}
variation.notes = ornamented;
}
3 => {
// Dynamic variation
for note in &mut variation.notes {
note.velocity = (note.velocity + rng.gen_range(-0.2..0.2)).clamp(0.1, 1.0);
}
}
_ => {}
}
variations_vec.push(variation);
}
variations_vec
}
}
/// Calculate least common multiple
fn lcm(a: usize, b: usize) -> usize {
a * b / gcd(a, b)
}
/// Calculate greatest common divisor
fn gcd(a: usize, b: usize) -> usize {
if b == 0 { a } else { gcd(b, a % b) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scales::{Scale, ScaleType};
#[test]
fn test_rhythm_pattern_creation() {
let pattern = RhythmPattern::new(16, 4);
assert_eq!(pattern.length, 16);
assert_eq!(pattern.steps_per_beat, 4);
assert_eq!(pattern.steps.len(), 16);
}
#[test]
fn test_kick_pattern() {
let pattern = RhythmPattern::kick_pattern();
assert!(pattern.steps[0].active);
assert!(pattern.steps[8].active);
assert!(pattern.steps[0].accent);
}
#[test]
fn test_euclidean_rhythm() {
let pattern = RhythmPattern::euclidean(3, 8);
let active_count = pattern.steps.iter().filter(|s| s.active).count();
assert_eq!(active_count, 3);
}
#[test]
fn test_pattern_rotation() {
let mut pattern = RhythmPattern::kick_pattern();
let original_first = pattern.steps[0].active;
pattern.rotate(1);
assert_eq!(pattern.steps[1].active, original_first);
}
#[test]
fn test_pattern_combination() {
let kick = RhythmPattern::kick_pattern();
let snare = RhythmPattern::snare_pattern();
let combined = kick.combine(&snare, CombineMode::Or);
// Should have hits from both patterns
assert!(combined.steps[0].active); // Kick
assert!(combined.steps[4].active); // Snare
assert!(combined.steps[8].active); // Kick
}
#[test]
fn test_melodic_pattern_creation() {
let scale = Scale::new(ScaleType::Major, 60);
let pattern = MelodicPattern::new(scale, 2);
assert_eq!(pattern.octave_range, 2);
assert_eq!(pattern.notes.len(), 0);
}
#[test]
fn test_ascending_pattern() {
let scale = Scale::new(ScaleType::Major, 60);
let mut pattern = MelodicPattern::new(scale, 1);
pattern.generate_ascending(1);
assert_eq!(pattern.notes.len(), 7); // 7 notes in major scale
assert_eq!(pattern.notes[0].scale_degree, 1);
assert_eq!(pattern.notes[6].scale_degree, 7);
}
#[test]
fn test_arpeggio_pattern() {
let scale = Scale::new(ScaleType::Major, 60);
let mut pattern = MelodicPattern::new(scale, 1);
pattern.generate_arpeggio(&[1, 3, 5], 1, 2);
assert_eq!(pattern.notes.len(), 6); // 3 notes × 2 repetitions
assert_eq!(pattern.notes[0].scale_degree, 1);
assert_eq!(pattern.notes[1].scale_degree, 3);
assert_eq!(pattern.notes[2].scale_degree, 5);
}
#[test]
fn test_midi_note_generation() {
let scale = Scale::new(ScaleType::Major, 60);
let mut pattern = MelodicPattern::new(scale, 1);
pattern.add_note(PatternNote::new(1, 0, 1.0, 0.8)); // Root note
pattern.add_note(PatternNote::new(3, 0, 1.0, 0.8)); // Third
let midi_notes = pattern.get_midi_notes(4);
assert_eq!(midi_notes.len(), 2);
assert_eq!(midi_notes[0].0, 60); // C4
assert_eq!(midi_notes[1].0, 64); // E4
}
#[test]
fn test_pattern_total_duration() {
let scale = Scale::new(ScaleType::Major, 60);
let mut pattern = MelodicPattern::new(scale, 1);
pattern.add_note(PatternNote::new(1, 0, 1.0, 0.8));
pattern.add_note(PatternNote::new(3, 0, 2.0, 0.8));
assert_eq!(pattern.total_duration(), 3.0);
}
#[test]
fn test_polyrhythm_creation() {
let pattern1 = RhythmPattern::euclidean(3, 4);
let pattern2 = RhythmPattern::euclidean(2, 3);
let patterns = vec![pattern1, pattern2];
let polyrhythm = PatternTransforms::create_polyrhythm(patterns);
assert_eq!(polyrhythm.length, 12); // LCM of 4 and 3
}
}