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}