Files
musicgen/src/patterns.rs
2025-06-20 18:52:55 -06:00

731 lines
22 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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
}
}