euclidean_rhythm/
lib.rs

1//! # Euclidean Rhythm
2//!
3//! A Rust library for generating Euclidean rhythms using Bjorklund's algorithm.
4//!
5//! Euclidean rhythms distribute pulses as evenly as possible across a given number
6//! of steps, creating musically interesting patterns found in traditional music
7//! from cultures worldwide.
8//!
9//! ## Quick Start
10//!
11//! ```
12//! use euclidean_rhythm::euclidean;
13//!
14//! // Generate a Cuban tresillo pattern: [x . . x . . x .]
15//! let pattern = euclidean(8, 3, 0);
16//!
17//! // Generate and rotate a West African bell pattern
18//! let pattern = euclidean(8, 5, 2);
19//! ```
20//!
21//! ## Algorithm Background
22//!
23//! The algorithm was developed by E. Bjorklund in 2003 for timing neutron beams
24//! at particle accelerators. In 2005, Godfried Toussaint discovered its musical
25//! applications, showing these patterns appear in traditional music worldwide.
26//!
27//! ## Common Patterns
28//!
29//! - **E(3,8)**: Cuban tresillo - `[x . . x . . x .]`
30//! - **E(5,8)**: West African bell - `[x . x x . x x .]`
31//! - **E(5,12)**: Persian rhythm - `[x . . x . x . . x . x .]`
32//! - **E(7,16)**: Brazilian bossa nova - `[x . . x . x . x . . x . x . x .]`
33//!
34//! ## References
35//!
36//! - Toussaint, G. (2005). "The Euclidean Algorithm Generates Traditional Musical Rhythms"
37//! - Bjorklund, E. (2003). "The Theory of Rep-Rate Pattern Generation in the SNS Timing System"
38
39/// Generates a Euclidean rhythm pattern using Bjorklund's algorithm.
40///
41/// Distributes `pulses` as evenly as possible across `steps`, optionally
42/// rotated by `rotation` positions (circular, wraps with modulo).
43///
44/// # Arguments
45/// * `steps` - Total number of steps in the pattern (1-64 recommended)
46/// * `pulses` - Number of pulses to distribute (0 to steps)
47/// * `rotation` - Number of positions to rotate the pattern (circular)
48///
49/// # Returns
50/// A vector of booleans where `true` represents a pulse and `false` represents a rest.
51///
52/// # Panics
53/// Panics if `pulses > steps` or if `steps == 0`.
54///
55/// # Examples
56/// ```
57/// use euclidean_rhythm::euclidean;
58/// let pattern = euclidean(8, 3, 0);
59/// assert_eq!(pattern, vec![true, false, false, true, false, false, true, false]);
60/// ```
61#[must_use = "euclidean rhythm pattern should be used"]
62pub fn euclidean(steps: u8, pulses: u8, rotation: u8) -> Vec<bool> {
63    if steps == 0 {
64        panic!("steps == 0");
65    }
66    if pulses > steps {
67        panic!("pulses > steps");
68    }
69    if pulses == 0 {
70        return vec![false; steps as usize];
71    }
72    if pulses == steps {
73        return vec![true; steps as usize];
74    }
75    let mut pattern = bjorklund(steps, pulses);
76
77    // Apply rotation
78    let rot = (rotation % steps) as usize;
79    if rot > 0 {
80        pattern.rotate_left(rot);
81    }
82
83    pattern
84}
85
86/// Converts a boolean pattern to a string representation.
87///
88/// # Arguments
89/// * `pattern` - The pattern to convert
90/// * `pulse_char` - Character to represent pulses (true values)
91/// * `rest_char` - Character to represent rests (false values)
92///
93/// # Examples
94/// ```
95/// use euclidean_rhythm::{euclidean, pattern_to_string};
96/// let pattern = euclidean(8, 3, 0);
97/// assert_eq!(pattern_to_string(&pattern, 'x', '.'), "x..x..x.");
98/// ```
99pub fn pattern_to_string(pattern: &[bool], pulse_char: char, rest_char: char) -> String {
100    pattern
101        .iter()
102        .map(|&b| if b { pulse_char } else { rest_char })
103        .collect()
104}
105
106/// Rotates a pattern by a given number of steps.
107///
108/// Positive rotation values rotate left (earlier in time), negative values rotate
109/// right (later in time). The rotation wraps around the pattern length.
110///
111/// # Arguments
112/// * `pattern` - The pattern to rotate
113/// * `rotation` - Number of positions to rotate (positive=left, negative=right)
114///
115/// # Examples
116/// ```
117/// use euclidean_rhythm::rotate_pattern;
118///
119/// let pattern = vec![true, false, true, false];
120/// let rotated = rotate_pattern(&pattern, 1);
121/// assert_eq!(rotated, vec![false, true, false, true]);
122///
123/// // Negative rotation (rotate right)
124/// let rotated = rotate_pattern(&pattern, -1);
125/// assert_eq!(rotated, vec![false, true, false, true]);
126/// ```
127pub fn rotate_pattern(pattern: &[bool], rotation: i32) -> Vec<bool> {
128    if pattern.is_empty() {
129        return Vec::new();
130    }
131
132    let len = pattern.len() as i32;
133    // Handle negative rotation and normalize to 0..len
134    let normalized_rotation = ((rotation % len) + len) % len;
135
136    let mut result = Vec::with_capacity(pattern.len());
137    result.extend_from_slice(&pattern[normalized_rotation as usize..]);
138    result.extend_from_slice(&pattern[..normalized_rotation as usize]);
139    result
140}
141
142/// Core Bjorklund algorithm implementation.
143///
144/// Distributes pulses evenly by repeatedly pairing and concatenating groups
145/// until a single pattern emerges. Uses Vec<Vec<bool>> to represent groups
146/// during algorithm execution, which is necessary for Bjorklund's pairing
147/// and concatenation logic. The final flattening step produces the output Vec<bool>.
148///
149/// Assumes inputs are already validated by the caller.
150#[inline]
151fn bjorklund(steps: u8, pulses: u8) -> Vec<bool> {
152    let steps = steps as usize;
153    let pulses = pulses as usize;
154
155    if pulses == 1 {
156        let mut pattern = vec![false; steps];
157        pattern[0] = true;
158        return pattern;
159    }
160
161    if pulses == 0 || pulses == steps {
162        // Trivial cases handled by caller
163        return Vec::new();
164    }
165
166    // Initialize with pulses as 1s and rests as 0s
167    let mut pattern: Vec<Vec<bool>> = Vec::new();
168
169    // Start with pulses number of [true] groups
170    for _ in 0..pulses {
171        pattern.push(vec![true]);
172    }
173
174    // And (steps - pulses) number of [false] groups
175    for _ in 0..(steps - pulses) {
176        pattern.push(vec![false]);
177    }
178
179    // Apply Bjorklund's algorithm: repeatedly distribute groups
180    let mut split_idx = pulses;
181
182    loop {
183        let num_left = split_idx;
184        let num_right = pattern.len() - split_idx;
185
186        if num_right <= 1 {
187            break;
188        }
189
190        let num_pairs = num_left.min(num_right);
191
192        // Append right groups to left groups
193        for i in 0..num_pairs {
194            let right_pattern = pattern[split_idx + i].clone();
195            pattern[i].extend(right_pattern);
196        }
197
198        // Remove the paired right groups
199        pattern.drain(split_idx..split_idx + num_pairs);
200
201        // Update split index
202        split_idx = num_pairs;
203    }
204
205    // Flatten the groups into a single pattern
206    pattern.into_iter().flatten().collect()
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn tresillo() {
215        let pattern = euclidean(8, 3, 0);
216        assert_eq!(
217            pattern,
218            vec![true, false, false, true, false, false, true, false]
219        );
220    }
221
222    #[test]
223    fn west_african_bell() {
224        let pattern = euclidean(8, 5, 0);
225        assert_eq!(
226            pattern,
227            vec![true, false, true, true, false, true, true, false]
228        );
229    }
230
231    #[test]
232    fn persian() {
233        let pattern = euclidean(12, 5, 0);
234        assert_eq!(
235            pattern,
236            vec![
237                true, false, false, true, false, true, false, false, true, false, true, false
238            ]
239        );
240    }
241
242    #[test]
243    fn bossa_nova() {
244        let pattern = euclidean(16, 7, 0);
245        assert_eq!(
246            pattern,
247            vec![
248                true, false, false, true, false, true, false, true, false, false, true, false,
249                true, false, true, false
250            ]
251        );
252    }
253
254    #[test]
255    fn all_pulses() {
256        let pattern = euclidean(8, 8, 0);
257        assert_eq!(pattern, vec![true; 8]);
258    }
259
260    #[test]
261    fn all_rests() {
262        let pattern = euclidean(8, 0, 0);
263        assert_eq!(pattern, vec![false; 8]);
264    }
265
266    #[test]
267    fn single_pulse() {
268        let pattern = euclidean(8, 1, 0);
269        assert_eq!(
270            pattern,
271            vec![true, false, false, false, false, false, false, false]
272        );
273    }
274
275    #[test]
276    #[should_panic]
277    fn pulses_gt_steps() {
278        let _ = euclidean(8, 9, 0);
279    }
280
281    #[test]
282    #[should_panic]
283    fn steps_zero() {
284        let _ = euclidean(0, 0, 0);
285    }
286
287    #[test]
288    fn rotation_wraps() {
289        let pattern = euclidean(8, 3, 10); // 10 % 8 == 2
290        assert_eq!(
291            pattern,
292            vec![false, true, false, false, true, false, true, false]
293        );
294    }
295
296    #[test]
297    fn large_steps() {
298        let pattern = euclidean(64, 5, 0);
299        assert_eq!(pattern.len(), 64);
300        assert_eq!(pattern.iter().filter(|&&x| x).count(), 5);
301    }
302
303    #[test]
304    fn pattern_length_always_matches_steps() {
305        // Property: The length of the output should always equal steps
306        for steps in 1..=32 {
307            for pulses in 0..=steps {
308                let pattern = euclidean(steps, pulses, 0);
309                assert_eq!(
310                    pattern.len(),
311                    steps as usize,
312                    "E({},{}) length mismatch",
313                    pulses,
314                    steps
315                );
316            }
317        }
318    }
319
320    #[test]
321    fn pulse_count_always_matches() {
322        // Property: The number of pulses in output should always equal pulses parameter
323        for steps in 1..=32 {
324            for pulses in 0..=steps {
325                let pattern = euclidean(steps, pulses, 0);
326                let actual_pulses = pattern.iter().filter(|&&x| x).count();
327                assert_eq!(
328                    actual_pulses, pulses as usize,
329                    "E({},{}) pulse count mismatch",
330                    pulses, steps
331                );
332            }
333        }
334    }
335
336    #[test]
337    fn rotation_by_one() {
338        let original = euclidean(8, 3, 0);
339        let rotated = euclidean(8, 3, 1);
340        // Verify the rotation shifted correctly
341        assert_eq!(rotated[0], original[1]);
342        assert_eq!(rotated[7], original[0]);
343    }
344
345    #[test]
346    fn pattern_to_string_works() {
347        let pattern = euclidean(8, 3, 0);
348        assert_eq!(pattern_to_string(&pattern, 'x', '.'), "x..x..x.");
349        assert_eq!(pattern_to_string(&pattern, '1', '0'), "10010010");
350    }
351
352    #[test]
353    fn rotate_pattern_works() {
354        let pattern = vec![true, false, false, true];
355
356        // Rotate left by 1
357        let rotated = rotate_pattern(&pattern, 1);
358        assert_eq!(rotated, vec![false, false, true, true]);
359
360        // Rotate right by 1 (negative rotation)
361        let rotated = rotate_pattern(&pattern, -1);
362        assert_eq!(rotated, vec![true, true, false, false]);
363
364        // Rotation wraps around
365        let rotated = rotate_pattern(&pattern, 5); // 5 % 4 = 1
366        assert_eq!(rotated, vec![false, false, true, true]);
367
368        // Zero rotation returns same pattern
369        let rotated = rotate_pattern(&pattern, 0);
370        assert_eq!(rotated, pattern);
371
372        // Empty pattern
373        let empty: Vec<bool> = vec![];
374        assert_eq!(rotate_pattern(&empty, 1), empty);
375    }
376}