Initial commit
This commit is contained in:
730
src/patterns.rs
Normal file
730
src/patterns.rs
Normal 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 °ree in °rees {
|
||||
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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user