1use crate::error::{PulseError, PulseResult};
4use crate::theory;
5use std::collections::{BTreeMap, HashMap};
6use tunes::sequences::{generative, mathematical as math, musical, rhythmic};
7
8const MAX_GENERATOR_SEQUENCE_LENGTH: usize = 1_000_000;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct GeneratorInfo {
13 pub name: &'static str,
15 pub category: &'static str,
17 pub output: &'static str,
19 pub parameters: &'static [&'static str],
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub enum PulseGenerated {
26 Numbers(Vec<f32>),
28 Integers(Vec<u32>),
30 Steps(Vec<usize>),
32 NumberMatrix(Vec<Vec<f32>>),
34 IntegerMatrix(Vec<Vec<u32>>),
36 StepMatrix(Vec<Vec<usize>>),
38 Text(String),
40}
41
42#[derive(Debug, Clone, PartialEq)]
44pub enum GeneratorOption {
45 Number(f64),
47 Integer(i64),
49 Boolean(bool),
51 Text(String),
53 IntegerList(Vec<u32>),
55 TextMap(BTreeMap<String, String>),
57}
58
59#[derive(Debug, Clone, Default, PartialEq)]
61pub struct GeneratorOptions {
62 values: BTreeMap<String, GeneratorOption>,
63}
64
65impl GeneratorOptions {
66 #[must_use]
68 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn insert(&mut self, name: impl Into<String>, value: GeneratorOption) {
74 self.values.insert(name.into(), value);
75 }
76
77 pub fn required_usize(&self, name: &str) -> PulseResult<usize> {
83 let value = self
84 .values
85 .get(name)
86 .ok_or_else(|| PulseError::MissingGeneratorOption {
87 name: name.to_string(),
88 })?;
89 option_to_positive_usize(name, value)
90 }
91
92 pub fn usize_or(&self, name: &str, default: usize) -> PulseResult<usize> {
98 match self.values.get(name) {
99 Some(value) => option_to_positive_usize(name, value),
100 None => Ok(default),
101 }
102 }
103
104 pub fn required_u32(&self, name: &str) -> PulseResult<u32> {
110 let value = self
111 .values
112 .get(name)
113 .ok_or_else(|| PulseError::MissingGeneratorOption {
114 name: name.to_string(),
115 })?;
116 option_to_u32(name, value)
117 }
118
119 pub fn u32_or(&self, name: &str, default: u32) -> PulseResult<u32> {
125 match self.values.get(name) {
126 Some(value) => option_to_u32(name, value),
127 None => Ok(default),
128 }
129 }
130
131 pub fn f32_or(&self, name: &str, default: f32) -> PulseResult<f32> {
137 match self.values.get(name) {
138 Some(value) => option_to_f32(name, value),
139 None => Ok(default),
140 }
141 }
142
143 pub fn required_f32(&self, name: &str) -> PulseResult<f32> {
149 let value = self
150 .values
151 .get(name)
152 .ok_or_else(|| PulseError::MissingGeneratorOption {
153 name: name.to_string(),
154 })?;
155 option_to_f32(name, value)
156 }
157
158 pub fn bool_or(&self, name: &str, default: bool) -> PulseResult<bool> {
164 match self.values.get(name) {
165 Some(GeneratorOption::Boolean(value)) => Ok(*value),
166 Some(value) => Err(PulseError::InvalidGeneratorOption {
167 name: name.to_string(),
168 value: option_value_to_string(value),
169 }),
170 None => Ok(default),
171 }
172 }
173
174 pub fn required_text(&self, name: &str) -> PulseResult<&str> {
180 match self.values.get(name) {
181 Some(GeneratorOption::Text(value)) => Ok(value),
182 Some(value) => Err(PulseError::InvalidGeneratorOption {
183 name: name.to_string(),
184 value: option_value_to_string(value),
185 }),
186 None => Err(PulseError::MissingGeneratorOption {
187 name: name.to_string(),
188 }),
189 }
190 }
191
192 pub fn required_text_map(&self, name: &str) -> PulseResult<&BTreeMap<String, String>> {
198 match self.values.get(name) {
199 Some(GeneratorOption::TextMap(value)) => Ok(value),
200 Some(value) => Err(PulseError::InvalidGeneratorOption {
201 name: name.to_string(),
202 value: option_value_to_string(value),
203 }),
204 None => Err(PulseError::MissingGeneratorOption {
205 name: name.to_string(),
206 }),
207 }
208 }
209}
210
211const LENGTH: &[&str] = &["length"];
212const START_STEP_LENGTH: &[&str] = &["start", "step", "length"];
213const START_RATIO_LENGTH: &[&str] = &["start", "ratio", "length"];
214const START_TERMS: &[&str] = &["start", "max_terms"];
215const CHAOS: &[&str] = &["r", "initial", "length"];
216const TENT: &[&str] = &["mu", "initial", "length"];
217const RANDOM_WALK: &[&str] = &["start", "step", "steps"];
218const BOUNDED_WALK: &[&str] = &["start", "step", "min", "max", "steps"];
219const VAN_DER_CORPUT: &[&str] = &["length", "base"];
220const PERLIN: &[&str] = &["seed", "frequency", "octaves", "persistence", "length"];
221const L_SYSTEM: &[&str] = &["axiom", "rules", "iterations"];
222const L_SYSTEM_VALUES: &[&str] = &["pattern"];
223const CELLULAR: &[&str] = &["rule", "width", "generations"];
224const CANTOR_SET: &[&str] = &["iterations", "resolution"];
225const FIFTHS: &[&str] = &["length", "start"];
226const HARMONIC: &[&str] = &["fundamental", "length"];
227const GOLDEN_SECTIONS: &[&str] = &["value", "divisions"];
228const EUCLIDEAN: &[&str] = &["pulses", "steps"];
229const SHEPARD: &[&str] = &["length", "steps_per_octave", "ascending"];
230const CIRCLE_MAP: &[&str] = &["omega", "k", "initial", "iterations"];
231
232const GENERATOR_INFOS: &[GeneratorInfo] = &[
233 info("fibonacci", "mathematical", "integers", LENGTH),
234 info("fibonacci_short", "mathematical", "integers", &[]),
235 info("fibonacci_medium", "mathematical", "integers", &[]),
236 info("fibonacci_classic", "mathematical", "integers", &[]),
237 info("fibonacci_long", "mathematical", "integers", &[]),
238 info("fibonacci_extended", "mathematical", "integers", &[]),
239 info("primes", "mathematical", "integers", LENGTH),
240 info("primes_short", "mathematical", "integers", &[]),
241 info("primes_medium", "mathematical", "integers", &[]),
242 info("primes_classic", "mathematical", "integers", &[]),
243 info("primes_long", "mathematical", "integers", &[]),
244 info("primes_polyrhythm", "mathematical", "integers", &[]),
245 info("arithmetic", "mathematical", "integers", START_STEP_LENGTH),
246 info("arithmetic_ascending", "mathematical", "integers", &[]),
247 info("arithmetic_evens", "mathematical", "integers", &[]),
248 info("arithmetic_odds", "mathematical", "integers", &[]),
249 info("arithmetic_fives", "mathematical", "integers", &[]),
250 info("arithmetic_whole_tone", "mathematical", "integers", &[]),
251 info("arithmetic_chromatic", "mathematical", "integers", &[]),
252 info("geometric", "mathematical", "integers", START_RATIO_LENGTH),
253 info("geometric_doubling", "mathematical", "integers", &[]),
254 info("geometric_tripling", "mathematical", "integers", &[]),
255 info("geometric_exponential", "mathematical", "integers", &[]),
256 info("geometric_moderate", "mathematical", "integers", &[]),
257 info("geometric_aggressive", "mathematical", "integers", &[]),
258 info("triangular", "mathematical", "integers", LENGTH),
259 info("triangular_short", "mathematical", "integers", &[]),
260 info("triangular_classic", "mathematical", "integers", &[]),
261 info("triangular_extended", "mathematical", "integers", &[]),
262 info("powers_of_two", "mathematical", "integers", LENGTH),
263 info("powers_of_two_short", "mathematical", "integers", &[]),
264 info("powers_of_two_classic", "mathematical", "integers", &[]),
265 info("powers_of_two_extended", "mathematical", "integers", &[]),
266 info("powers_of_two_byte", "mathematical", "integers", &[]),
267 info("collatz", "mathematical", "integers", START_TERMS),
268 info("collatz_short", "mathematical", "integers", &[]),
269 info("collatz_medium", "mathematical", "integers", &[]),
270 info("collatz_classic", "mathematical", "integers", &[]),
271 info("collatz_dramatic", "mathematical", "integers", &[]),
272 info("collatz_epic", "mathematical", "integers", &[]),
273 info("collatz_sparse", "mathematical", "integers", &[]),
274 info("lucas", "mathematical", "integers", LENGTH),
275 info("lucas_short", "mathematical", "integers", &[]),
276 info("lucas_classic", "mathematical", "integers", &[]),
277 info("lucas_extended", "mathematical", "integers", &[]),
278 info("catalan", "mathematical", "integers", LENGTH),
279 info("catalan_short", "mathematical", "integers", &[]),
280 info("catalan_classic", "mathematical", "integers", &[]),
281 info("catalan_extended", "mathematical", "integers", &[]),
282 info("padovan", "mathematical", "integers", LENGTH),
283 info("padovan_short", "mathematical", "integers", &[]),
284 info("padovan_classic", "mathematical", "integers", &[]),
285 info("padovan_extended", "mathematical", "integers", &[]),
286 info("pell", "mathematical", "integers", LENGTH),
287 info("pell_short", "mathematical", "integers", &[]),
288 info("pell_classic", "mathematical", "integers", &[]),
289 info("pell_extended", "mathematical", "integers", &[]),
290 info("pentagonal", "mathematical", "integers", LENGTH),
291 info("pentagonal_short", "mathematical", "integers", &[]),
292 info("pentagonal_classic", "mathematical", "integers", &[]),
293 info("pentagonal_extended", "mathematical", "integers", &[]),
294 info("random_walk", "generative", "numbers", RANDOM_WALK),
295 info("random_walk_short", "generative", "numbers", &[]),
296 info("random_walk_classic", "generative", "numbers", &[]),
297 info("random_walk_long", "generative", "numbers", &[]),
298 info("random_walk_large_steps", "generative", "numbers", &[]),
299 info("bounded_walk", "generative", "numbers", BOUNDED_WALK),
300 info("bounded_walk_classic", "generative", "numbers", &[]),
301 info("bounded_walk_narrow", "generative", "numbers", &[]),
302 info("bounded_walk_wide", "generative", "numbers", &[]),
303 info("logistic_map", "generative", "numbers", CHAOS),
304 info("logistic_ordered", "generative", "numbers", &[]),
305 info("logistic_oscillating", "generative", "numbers", &[]),
306 info("logistic_edge_of_chaos", "generative", "numbers", &[]),
307 info("logistic_chaotic", "generative", "numbers", &[]),
308 info("logistic_classic", "generative", "numbers", &[]),
309 info("tent_map", "generative", "numbers", TENT),
310 info("tent_chaotic", "generative", "numbers", LENGTH),
311 info("tent_moderate", "generative", "numbers", LENGTH),
312 info("tent_mild", "generative", "numbers", LENGTH),
313 info("sine_map", "generative", "numbers", CHAOS),
314 info("sine_smooth", "generative", "numbers", LENGTH),
315 info("sine_chaotic", "generative", "numbers", LENGTH),
316 info("sine_gentle", "generative", "numbers", LENGTH),
317 info("thue_morse", "generative", "integers", LENGTH),
318 info("thue_morse_short", "generative", "integers", &[]),
319 info("thue_morse_classic", "generative", "integers", &[]),
320 info("thue_morse_extended", "generative", "integers", &[]),
321 info("recaman", "generative", "integers", LENGTH),
322 info("recaman_short", "generative", "integers", &[]),
323 info("recaman_classic", "generative", "integers", &[]),
324 info("recaman_long", "generative", "integers", &[]),
325 info("recaman_epic", "generative", "integers", &[]),
326 info("van_der_corput", "generative", "numbers", VAN_DER_CORPUT),
327 info("van_der_corput_short", "generative", "numbers", &[]),
328 info("van_der_corput_classic", "generative", "numbers", &[]),
329 info("van_der_corput_long", "generative", "numbers", &[]),
330 info("van_der_corput_ternary", "generative", "numbers", &[]),
331 info("van_der_corput_pentary", "generative", "numbers", &[]),
332 info("perlin_noise", "generative", "numbers", PERLIN),
333 info("perlin_smooth_drift", "generative", "numbers", &[]),
334 info("perlin_classic", "generative", "numbers", &[]),
335 info("perlin_rich_texture", "generative", "numbers", &[]),
336 info("perlin_fast_modulation", "generative", "numbers", &[]),
337 info("perlin_ambient_pad", "generative", "numbers", &[]),
338 info("l_system", "generative", "text", L_SYSTEM),
339 info("l_system_values", "generative", "integers", L_SYSTEM_VALUES),
340 info("l_system_fibonacci", "generative", "text", &[]),
341 info("l_system_thue_morse", "generative", "text", &[]),
342 info("l_system_cantor", "generative", "text", &[]),
343 info("l_system_binary_tree", "generative", "text", &[]),
344 info("l_system_classic", "generative", "text", &[]),
345 info(
346 "cellular_automaton",
347 "generative",
348 "integer_matrix",
349 CELLULAR,
350 ),
351 info("cellular_rule30", "generative", "integer_matrix", &[]),
352 info("cellular_rule110", "generative", "integer_matrix", &[]),
353 info("cellular_rule90", "generative", "integer_matrix", &[]),
354 info("cellular_rule184", "generative", "integer_matrix", &[]),
355 info("cellular_classic", "generative", "integer_matrix", &[]),
356 info("cantor_set", "generative", "integers", CANTOR_SET),
357 info("cantor_rhythm_16", "generative", "steps", &[]),
358 info("cantor_rhythm_27", "generative", "steps", &[]),
359 info("cantor_rhythm_9", "generative", "steps", &[]),
360 info("cantor_rhythm_81", "generative", "steps", &[]),
361 info("markov", "generative", "integers", &[]),
362 info("markov_c_major_melody", "generative", "integers", &[]),
363 info("markov_up_down", "generative", "integers", &[]),
364 info("circle_of_fifths", "musical", "integers", FIFTHS),
365 info("circle_of_fourths", "musical", "integers", FIFTHS),
366 info("harmonic_series", "musical", "numbers", HARMONIC),
367 info("undertone_series", "musical", "numbers", LENGTH),
368 info("golden_ratio", "musical", "numbers", LENGTH),
369 info("golden_sections", "musical", "numbers", GOLDEN_SECTIONS),
370 info("pythagorean_tuning", "musical", "numbers", LENGTH),
371 info("just_intonation_major", "musical", "numbers", &[]),
372 info("just_intonation_minor", "musical", "numbers", &[]),
373 info("euclidean", "rhythmic", "steps", EUCLIDEAN),
374 info("euclidean_pattern", "rhythmic", "integers", EUCLIDEAN),
375 info("golden_ratio_rhythm", "rhythmic", "steps", &["steps"]),
376 info("shepard_tone", "rhythmic", "integers", SHEPARD),
377 info("circle_map", "rhythmic", "numbers", CIRCLE_MAP),
378];
379
380const fn info(
381 name: &'static str,
382 category: &'static str,
383 output: &'static str,
384 parameters: &'static [&'static str],
385) -> GeneratorInfo {
386 GeneratorInfo {
387 name,
388 category,
389 output,
390 parameters,
391 }
392}
393
394#[must_use]
396pub fn generator_infos() -> &'static [GeneratorInfo] {
397 GENERATOR_INFOS
398}
399
400#[must_use]
402pub fn generator_names() -> Vec<&'static str> {
403 generator_infos().iter().map(|info| info.name).collect()
404}
405
406pub fn generator_info(name: &str) -> PulseResult<&'static GeneratorInfo> {
412 generator_infos()
413 .iter()
414 .find(|info| info.name == name)
415 .ok_or_else(|| PulseError::InvalidGenerator {
416 name: name.to_string(),
417 })
418}
419
420pub fn generate(name: &str, options: &GeneratorOptions) -> PulseResult<PulseGenerated> {
427 match name {
428 "fibonacci" => Ok(integers(math::fibonacci::generate(
429 options.required_usize("length")?,
430 ))),
431 "fibonacci_short" => Ok(integers(math::fibonacci::short())),
432 "fibonacci_medium" => Ok(integers(math::fibonacci::medium())),
433 "fibonacci_classic" => Ok(integers(math::fibonacci::classic())),
434 "fibonacci_long" => Ok(integers(math::fibonacci::long())),
435 "fibonacci_extended" => Ok(integers(math::fibonacci::extended())),
436 "primes" => Ok(integers(math::primes::generate(
437 options.required_usize("length")?,
438 ))),
439 "primes_short" => Ok(integers(math::primes::short())),
440 "primes_medium" => Ok(integers(math::primes::medium())),
441 "primes_classic" => Ok(integers(math::primes::classic())),
442 "primes_long" => Ok(integers(math::primes::long())),
443 "primes_polyrhythm" => Ok(integers(math::primes::polyrhythm())),
444 "arithmetic" => Ok(integers(math::arithmetic::generate(
445 options.u32_or("start", 0)?,
446 options.u32_or("step", 1)?,
447 options.required_usize("length")?,
448 ))),
449 "arithmetic_ascending" => Ok(integers(math::arithmetic::ascending())),
450 "arithmetic_evens" => Ok(integers(math::arithmetic::evens())),
451 "arithmetic_odds" => Ok(integers(math::arithmetic::odds())),
452 "arithmetic_fives" => Ok(integers(math::arithmetic::fives())),
453 "arithmetic_whole_tone" => Ok(integers(math::arithmetic::whole_tone())),
454 "arithmetic_chromatic" => Ok(integers(math::arithmetic::chromatic())),
455 "geometric" => Ok(integers(math::geometric::generate(
456 options.u32_or("start", 1)?,
457 options.u32_or("ratio", 2)?,
458 options.required_usize("length")?,
459 ))),
460 "geometric_doubling" => Ok(integers(math::geometric::doubling())),
461 "geometric_tripling" => Ok(integers(math::geometric::tripling())),
462 "geometric_exponential" => Ok(integers(math::geometric::exponential())),
463 "geometric_moderate" => Ok(integers(math::geometric::moderate())),
464 "geometric_aggressive" => Ok(integers(math::geometric::aggressive())),
465 "triangular" => Ok(integers(math::triangular::generate(
466 options.required_usize("length")?,
467 ))),
468 "triangular_short" => Ok(integers(math::triangular::short())),
469 "triangular_classic" => Ok(integers(math::triangular::classic())),
470 "triangular_extended" => Ok(integers(math::triangular::extended())),
471 "powers_of_two" => Ok(integers(math::powers_of_two::generate(
472 options.required_usize("length")?,
473 ))),
474 "powers_of_two_short" => Ok(integers(math::powers_of_two::short())),
475 "powers_of_two_classic" => Ok(integers(math::powers_of_two::classic())),
476 "powers_of_two_extended" => Ok(integers(math::powers_of_two::extended())),
477 "powers_of_two_byte" => Ok(integers(math::powers_of_two::byte())),
478 "collatz" => Ok(integers(math::collatz::generate(
479 options.u32_or("start", 27)?,
480 options.required_usize("max_terms")?,
481 ))),
482 "collatz_short" => Ok(integers(math::collatz::short())),
483 "collatz_medium" => Ok(integers(math::collatz::medium())),
484 "collatz_classic" => Ok(integers(math::collatz::classic())),
485 "collatz_dramatic" => Ok(integers(math::collatz::dramatic())),
486 "collatz_epic" => Ok(integers(math::collatz::epic())),
487 "collatz_sparse" => Ok(integers(math::collatz::sparse())),
488 "lucas" => Ok(integers(math::lucas::generate(
489 options.required_usize("length")?,
490 ))),
491 "lucas_short" => Ok(integers(math::lucas::short())),
492 "lucas_classic" => Ok(integers(math::lucas::classic())),
493 "lucas_extended" => Ok(integers(math::lucas::extended())),
494 "catalan" => Ok(integers(math::catalan::generate(
495 options.required_usize("length")?,
496 ))),
497 "catalan_short" => Ok(integers(math::catalan::short())),
498 "catalan_classic" => Ok(integers(math::catalan::classic())),
499 "catalan_extended" => Ok(integers(math::catalan::extended())),
500 "padovan" => Ok(integers(math::padovan::generate(
501 options.required_usize("length")?,
502 ))),
503 "padovan_short" => Ok(integers(math::padovan::short())),
504 "padovan_classic" => Ok(integers(math::padovan::classic())),
505 "padovan_extended" => Ok(integers(math::padovan::extended())),
506 "pell" => Ok(integers(math::pell::generate(
507 options.required_usize("length")?,
508 ))),
509 "pell_short" => Ok(integers(math::pell::short())),
510 "pell_classic" => Ok(integers(math::pell::classic())),
511 "pell_extended" => Ok(integers(math::pell::extended())),
512 "pentagonal" => Ok(integers(math::pentagonal::generate(
513 options.required_usize("length")?,
514 ))),
515 "pentagonal_short" => Ok(integers(math::pentagonal::short())),
516 "pentagonal_classic" => Ok(integers(math::pentagonal::classic())),
517 "pentagonal_extended" => Ok(integers(math::pentagonal::extended())),
518 "random_walk" => Ok(numbers(generative::random_walk::generate(
519 options.f32_or("start", 0.0)?,
520 options.f32_or("step", 1.0)?,
521 options.required_usize("steps")?,
522 ))),
523 "random_walk_short" => Ok(numbers(generative::random_walk::short())),
524 "random_walk_classic" => Ok(numbers(generative::random_walk::classic())),
525 "random_walk_long" => Ok(numbers(generative::random_walk::long())),
526 "random_walk_large_steps" => Ok(numbers(generative::random_walk::large_steps())),
527 "bounded_walk" => Ok(numbers(generative::bounded_walk::generate(
528 options.f32_or("start", 0.0)?,
529 options.f32_or("step", 1.0)?,
530 options.f32_or("min", -12.0)?,
531 options.f32_or("max", 12.0)?,
532 options.required_usize("steps")?,
533 ))),
534 "bounded_walk_classic" => Ok(numbers(generative::bounded_walk::classic())),
535 "bounded_walk_narrow" => Ok(numbers(generative::bounded_walk::narrow())),
536 "bounded_walk_wide" => Ok(numbers(generative::bounded_walk::wide())),
537 "logistic_map" => Ok(numbers(generative::logistic_map::generate(
538 options.f32_or("r", 3.9)?,
539 options.f32_or("initial", 0.5)?,
540 options.required_usize("length")?,
541 ))),
542 "logistic_ordered" => Ok(numbers(generative::logistic_map::ordered())),
543 "logistic_oscillating" => Ok(numbers(generative::logistic_map::oscillating())),
544 "logistic_edge_of_chaos" => Ok(numbers(generative::logistic_map::edge_of_chaos())),
545 "logistic_chaotic" => Ok(numbers(generative::logistic_map::chaotic())),
546 "logistic_classic" => Ok(numbers(generative::logistic_map::classic())),
547 "tent_map" => Ok(numbers(generative::tent_map::generate(
548 options.f32_or("mu", 2.0)?,
549 options.f32_or("initial", 0.5)?,
550 options.required_usize("length")?,
551 ))),
552 "tent_chaotic" => Ok(numbers(generative::tent_map::tent_chaotic(
553 options.required_usize("length")?,
554 ))),
555 "tent_moderate" => Ok(numbers(generative::tent_map::tent_moderate(
556 options.required_usize("length")?,
557 ))),
558 "tent_mild" => Ok(numbers(generative::tent_map::tent_mild(
559 options.required_usize("length")?,
560 ))),
561 "sine_map" => Ok(numbers(generative::sine_map::generate(
562 options.f32_or("r", 0.9)?,
563 options.f32_or("initial", 0.5)?,
564 options.required_usize("length")?,
565 ))),
566 "sine_smooth" => Ok(numbers(generative::sine_map::sine_smooth(
567 options.required_usize("length")?,
568 ))),
569 "sine_chaotic" => Ok(numbers(generative::sine_map::sine_chaotic(
570 options.required_usize("length")?,
571 ))),
572 "sine_gentle" => Ok(numbers(generative::sine_map::sine_gentle(
573 options.required_usize("length")?,
574 ))),
575 "thue_morse" => Ok(integers(generative::thue_morse::generate(
576 options.required_usize("length")?,
577 ))),
578 "thue_morse_short" => Ok(integers(generative::thue_morse::short())),
579 "thue_morse_classic" => Ok(integers(generative::thue_morse::classic())),
580 "thue_morse_extended" => Ok(integers(generative::thue_morse::extended())),
581 "recaman" => Ok(integers(generative::recaman::generate(
582 options.required_usize("length")?,
583 ))),
584 "recaman_short" => Ok(integers(generative::recaman::short())),
585 "recaman_classic" => Ok(integers(generative::recaman::classic())),
586 "recaman_long" => Ok(integers(generative::recaman::long())),
587 "recaman_epic" => Ok(integers(generative::recaman::epic())),
588 "van_der_corput" => Ok(numbers(generative::van_der_corput::generate(
589 options.required_usize("length")?,
590 options.u32_or("base", 2)?,
591 ))),
592 "van_der_corput_short" => Ok(numbers(generative::van_der_corput::short())),
593 "van_der_corput_classic" => Ok(numbers(generative::van_der_corput::classic())),
594 "van_der_corput_long" => Ok(numbers(generative::van_der_corput::long())),
595 "van_der_corput_ternary" => Ok(numbers(generative::van_der_corput::ternary())),
596 "van_der_corput_pentary" => Ok(numbers(generative::van_der_corput::pentary())),
597 "perlin_noise" => Ok(numbers(generative::perlin_noise::generate(
598 options.u32_or("seed", 42)?,
599 options.f32_or("frequency", 0.1)?,
600 options.u32_or("octaves", 3)?,
601 options.f32_or("persistence", 0.5)?,
602 options.required_usize("length")?,
603 ))),
604 "perlin_smooth_drift" => Ok(numbers(generative::perlin_noise::smooth_drift())),
605 "perlin_classic" => Ok(numbers(generative::perlin_noise::classic())),
606 "perlin_rich_texture" => Ok(numbers(generative::perlin_noise::rich_texture())),
607 "perlin_fast_modulation" => Ok(numbers(generative::perlin_noise::fast_modulation())),
608 "perlin_ambient_pad" => Ok(numbers(generative::perlin_noise::ambient_pad())),
609 "l_system" => Ok(PulseGenerated::Text(generative::lsystem::generate(
610 options.required_text("axiom")?,
611 &lsystem_rules(options)?,
612 options.required_usize("iterations")?,
613 ))),
614 "l_system_values" => Ok(integers(generative::lsystem::lsystem_to_sequence(
615 options.required_text("pattern")?,
616 ))),
617 "l_system_fibonacci" => Ok(PulseGenerated::Text(generative::lsystem::fibonacci())),
618 "l_system_thue_morse" => Ok(PulseGenerated::Text(generative::lsystem::thue_morse())),
619 "l_system_cantor" => Ok(PulseGenerated::Text(generative::lsystem::cantor())),
620 "l_system_binary_tree" => Ok(PulseGenerated::Text(generative::lsystem::binary_tree())),
621 "l_system_classic" => Ok(PulseGenerated::Text(generative::lsystem::classic())),
622 "cellular_automaton" => {
623 let generations = options.required_usize("generations")?;
624 let width = options.required_usize("width")?;
625 assert_product_within_sequence_limit("generations*width", generations, width)?;
626 Ok(integer_matrix(generative::cellular_automaton::generate(
627 option_u8_or(options, "rule", 30)?,
628 generations,
629 width,
630 None,
631 )))
632 }
633 "cellular_rule30" => Ok(integer_matrix(generative::cellular_automaton::rule30())),
634 "cellular_rule110" => Ok(integer_matrix(generative::cellular_automaton::rule110())),
635 "cellular_rule90" => Ok(integer_matrix(generative::cellular_automaton::rule90())),
636 "cellular_rule184" => Ok(integer_matrix(generative::cellular_automaton::rule184())),
637 "cellular_classic" => Ok(integer_matrix(generative::cellular_automaton::classic())),
638 "cantor_set" => Ok(integers(generative::cantor_set::generate(
639 options.required_usize("iterations")?,
640 options.required_usize("resolution")?,
641 ))),
642 "cantor_rhythm_16" => Ok(steps(generative::cantor_set::cantor_rhythm_16())),
643 "cantor_rhythm_27" => Ok(steps(generative::cantor_set::cantor_rhythm_27())),
644 "cantor_rhythm_9" => Ok(steps(generative::cantor_set::cantor_rhythm_9())),
645 "cantor_rhythm_81" => Ok(steps(generative::cantor_set::cantor_rhythm_81())),
646 "markov" | "markov_c_major_melody" => Ok(integers(generative::markov::c_major_melody())),
647 "markov_up_down" => Ok(integers(generative::markov::up_down())),
648 "circle_of_fifths" => Ok(integers_from_u8(
649 musical::circle_of_fifths::circle_of_fifths(
650 options.required_usize("length")?,
651 option_u8_or(options, "start", 0)?,
652 ),
653 )),
654 "circle_of_fourths" => Ok(integers_from_u8(
655 musical::circle_of_fifths::circle_of_fourths(
656 options.required_usize("length")?,
657 option_u8_or(options, "start", 0)?,
658 ),
659 )),
660 "harmonic_series" => Ok(numbers(musical::harmonic_series::harmonic_series(
661 options.required_f32("fundamental")?,
662 options.required_usize("length")?,
663 ))),
664 "undertone_series" => Ok(numbers(musical::undertone_series::undertone_series(
665 options.required_usize("length")?,
666 ))),
667 "golden_ratio" => Ok(numbers(musical::golden_ratio::golden_ratio(
668 options.required_usize("length")?,
669 ))),
670 "golden_sections" => Ok(numbers(musical::golden_sections::golden_sections(
671 options.required_f32("value")?,
672 options.required_usize("divisions")?,
673 ))),
674 "pythagorean_tuning" => Ok(numbers(musical::pythagorean_tuning::pythagorean_tuning(
675 options.required_usize("length")?,
676 ))),
677 "just_intonation_major" => {
678 Ok(numbers(musical::pythagorean_tuning::just_intonation_major()))
679 }
680 "just_intonation_minor" => {
681 Ok(numbers(musical::pythagorean_tuning::just_intonation_minor()))
682 }
683 "euclidean" => Ok(steps(rhythmic::euclidean::generate(
684 options.required_usize("pulses")?,
685 options.required_usize("steps")?,
686 ))),
687 "euclidean_pattern" => Ok(integers(rhythmic::euclidean::pattern(
688 options.required_usize("pulses")?,
689 options.required_usize("steps")?,
690 ))),
691 "golden_ratio_rhythm" => Ok(steps(rhythmic::golden_ratio_rhythm::generate(
692 options.required_usize("steps")?,
693 ))),
694 "shepard_tone" => Ok(integers(rhythmic::shepard_tone::generate(
695 options.required_usize("length")?,
696 options.u32_or("steps_per_octave", 12)?,
697 options.bool_or("ascending", true)?,
698 ))),
699 "circle_map" => Ok(numbers(rhythmic::circle_map::generate(
700 options.f32_or("omega", 0.618_034)?,
701 options.f32_or("k", 1.5)?,
702 options.f32_or("initial", 0.0)?,
703 options.required_usize("iterations")?,
704 ))),
705 _ => Err(PulseError::InvalidGenerator {
706 name: name.to_string(),
707 }),
708 }
709}
710
711pub fn normalize_values(values: &[f32], min: f32, max: f32) -> PulseResult<Vec<f32>> {
718 validate_value_slice("values", values)?;
719 if !min.is_finite() || !max.is_finite() || min >= max {
720 return Err(PulseError::InvalidGeneratorOption {
721 name: "range".to_string(),
722 value: format!("{min}..{max}"),
723 });
724 }
725
726 Ok(musical::normalize::normalize_f32(values, min, max))
727}
728
729pub fn map_values_to_scale(
736 values: &[f32],
737 scale: &str,
738 root: &str,
739 octaves: u32,
740) -> PulseResult<Vec<f32>> {
741 validate_value_slice("values", values)?;
742 if octaves == 0 {
743 return Err(PulseError::InvalidGeneratorOption {
744 name: "octaves".to_string(),
745 value: octaves.to_string(),
746 });
747 }
748
749 let scale = scale_intervals(scale)?;
750 let root = theory::parse_note_frequency(root)?;
751 Ok(musical::map_to_scale::map_to_scale_f32(
752 values, &scale, root, octaves,
753 ))
754}
755
756fn numbers(values: Vec<f32>) -> PulseGenerated {
757 PulseGenerated::Numbers(values)
758}
759
760fn integers(values: Vec<u32>) -> PulseGenerated {
761 PulseGenerated::Integers(values)
762}
763
764fn integers_from_u8(values: Vec<u8>) -> PulseGenerated {
765 PulseGenerated::Integers(values.into_iter().map(u32::from).collect())
766}
767
768fn steps(values: Vec<usize>) -> PulseGenerated {
769 PulseGenerated::Steps(values)
770}
771
772fn integer_matrix(values: Vec<Vec<u32>>) -> PulseGenerated {
773 PulseGenerated::IntegerMatrix(values)
774}
775
776fn lsystem_rules(options: &GeneratorOptions) -> PulseResult<HashMap<char, String>> {
777 let mut rules = HashMap::new();
778
779 for (key, value) in options.required_text_map("rules")? {
780 let mut chars = key.chars();
781 let Some(symbol) = chars.next() else {
782 return Err(PulseError::InvalidGeneratorOption {
783 name: "rules".to_string(),
784 value: key.clone(),
785 });
786 };
787 if chars.next().is_some() {
788 return Err(PulseError::InvalidGeneratorOption {
789 name: "rules".to_string(),
790 value: key.clone(),
791 });
792 }
793 rules.insert(symbol, value.clone());
794 }
795
796 Ok(rules)
797}
798
799fn option_u8_or(options: &GeneratorOptions, name: &str, default: u8) -> PulseResult<u8> {
800 let value = options.u32_or(name, u32::from(default))?;
801 u8::try_from(value).map_err(|_| PulseError::InvalidGeneratorOption {
802 name: name.to_string(),
803 value: value.to_string(),
804 })
805}
806
807fn validate_value_slice(name: &str, values: &[f32]) -> PulseResult<()> {
808 if values.is_empty() {
809 return Err(PulseError::InvalidGeneratorOption {
810 name: name.to_string(),
811 value: "empty".to_string(),
812 });
813 }
814
815 if values.iter().any(|value| !value.is_finite()) {
816 return Err(PulseError::InvalidGeneratorOption {
817 name: name.to_string(),
818 value: "non-finite".to_string(),
819 });
820 }
821
822 Ok(())
823}
824
825fn scale_intervals(scale: &str) -> PulseResult<Vec<u32>> {
826 let normalized = scale.to_ascii_lowercase();
827 let intervals = match normalized.as_str() {
828 "major" | "ionian" => musical::map_to_scale::Scale::major(),
829 "minor" | "aeolian" => musical::map_to_scale::Scale::minor(),
830 "major_pentatonic" => musical::map_to_scale::Scale::major_pentatonic(),
831 "minor_pentatonic" => musical::map_to_scale::Scale::minor_pentatonic(),
832 "harmonic_minor" => musical::map_to_scale::Scale::harmonic_minor(),
833 "blues" => musical::map_to_scale::Scale::blues(),
834 "chromatic" => musical::map_to_scale::Scale::chromatic(),
835 "whole_tone" => musical::map_to_scale::Scale::whole_tone(),
836 "dorian" => musical::map_to_scale::Scale::dorian(),
837 "phrygian" => musical::map_to_scale::Scale::phrygian(),
838 "lydian" => musical::map_to_scale::Scale::lydian(),
839 "mixolydian" => musical::map_to_scale::Scale::mixolydian(),
840 _ => {
841 return Err(PulseError::InvalidGeneratorOption {
842 name: "scale".to_string(),
843 value: scale.to_string(),
844 });
845 }
846 };
847 Ok(intervals)
848}
849
850fn option_to_positive_usize(name: &str, value: &GeneratorOption) -> PulseResult<usize> {
851 let parsed = match value {
852 GeneratorOption::Integer(value) if *value > 0 => usize::try_from(*value)
853 .ok()
854 .filter(|value| *value <= MAX_GENERATOR_SEQUENCE_LENGTH),
855 GeneratorOption::Number(value)
856 if value.is_finite()
857 && *value > 0.0
858 && *value <= MAX_GENERATOR_SEQUENCE_LENGTH as f64
859 && *value < usize_upper_bound_as_f64()
860 && value.fract() == 0.0 =>
861 {
862 usize::try_from(*value as u64).ok()
863 }
864 _ => None,
865 };
866
867 parsed.ok_or_else(|| PulseError::InvalidGeneratorOption {
868 name: name.to_string(),
869 value: option_value_to_string(value),
870 })
871}
872
873fn usize_upper_bound_as_f64() -> f64 {
874 let upper = usize::MAX as f64;
875 if (upper as u128) > usize::MAX as u128 {
876 upper
877 } else {
878 upper + 1.0
879 }
880}
881
882fn assert_product_within_sequence_limit(name: &str, a: usize, b: usize) -> PulseResult<()> {
892 a.checked_mul(b)
893 .filter(|&product| product <= MAX_GENERATOR_SEQUENCE_LENGTH)
894 .ok_or_else(|| PulseError::InvalidGeneratorOption {
895 name: name.to_string(),
896 value: format!("{a}*{b}"),
897 })?;
898 Ok(())
899}
900fn option_to_u32(name: &str, value: &GeneratorOption) -> PulseResult<u32> {
901 let parsed = match value {
902 GeneratorOption::Integer(value) if *value >= 0 => u32::try_from(*value).ok(),
903 GeneratorOption::Number(value)
904 if value.is_finite()
905 && *value >= 0.0
906 && *value <= f64::from(u32::MAX)
907 && value.fract() == 0.0 =>
908 {
909 u32::try_from(*value as u64).ok()
910 }
911 _ => None,
912 };
913
914 parsed.ok_or_else(|| PulseError::InvalidGeneratorOption {
915 name: name.to_string(),
916 value: option_value_to_string(value),
917 })
918}
919
920fn option_to_f32(name: &str, value: &GeneratorOption) -> PulseResult<f32> {
921 let parsed = match value {
922 GeneratorOption::Integer(value) => Some(*value as f32),
923 GeneratorOption::Number(value)
924 if value.is_finite()
925 && *value >= f64::from(f32::MIN)
926 && *value <= f64::from(f32::MAX) =>
927 {
928 Some(*value as f32)
929 }
930 _ => None,
931 };
932
933 parsed.ok_or_else(|| PulseError::InvalidGeneratorOption {
934 name: name.to_string(),
935 value: option_value_to_string(value),
936 })
937}
938
939fn option_value_to_string(value: &GeneratorOption) -> String {
940 match value {
941 GeneratorOption::Number(value) => value.to_string(),
942 GeneratorOption::Integer(value) => value.to_string(),
943 GeneratorOption::Boolean(value) => value.to_string(),
944 GeneratorOption::Text(value) => value.clone(),
945 GeneratorOption::IntegerList(values) => format!("{values:?}"),
946 GeneratorOption::TextMap(values) => format!("{values:?}"),
947 }
948}
949
950#[cfg(test)]
951mod tests {
952 use super::*;
953
954 #[test]
955 fn catalog_has_required_names_and_enough_entries() {
956 let names = generator_names();
957
958 assert!(names.len() >= 50);
959 assert!(names.contains(&"fibonacci"));
960 assert!(names.contains(&"primes"));
961 assert!(names.contains(&"l_system"));
962 assert!(names.contains(&"cellular_automaton"));
963 assert!(names.contains(&"logistic_map"));
964 assert!(names.contains(&"cantor_rhythm_16"));
965 }
966
967 #[test]
968 fn generates_basic_sequences() {
969 let mut options = GeneratorOptions::new();
970 options.insert("length", GeneratorOption::Integer(8));
971
972 assert_eq!(
973 generate("fibonacci", &options).unwrap(),
974 PulseGenerated::Integers(vec![1, 1, 2, 3, 5, 8, 13, 21])
975 );
976 assert_eq!(
977 generate("primes", &options).unwrap(),
978 PulseGenerated::Integers(vec![2, 3, 5, 7, 11, 13, 17, 19])
979 );
980 }
981
982 #[test]
983 fn usize_options_reject_numbers_larger_than_usize() {
984 let mut options = GeneratorOptions::new();
985 options.insert("length", GeneratorOption::Number(1.0e100));
986
987 let error = options
988 .required_usize("length")
989 .expect_err("oversized numeric length should fail before generation");
990
991 assert!(matches!(
992 error,
993 PulseError::InvalidGeneratorOption { ref name, .. } if name == "length"
994 ));
995 }
996
997 #[test]
998 fn usize_options_reject_unrenderable_sequence_lengths() {
999 let mut options = GeneratorOptions::new();
1000 options.insert("length", GeneratorOption::Integer(1_000_001));
1001
1002 let error = options
1003 .required_usize("length")
1004 .expect_err("oversized generator length should fail before allocation");
1005
1006 assert!(matches!(
1007 error,
1008 PulseError::InvalidGeneratorOption { ref name, .. } if name == "length"
1009 ));
1010 }
1011
1012 #[test]
1013 fn cellular_automaton_rejects_unrenderable_matrix_size() {
1014 let mut options = GeneratorOptions::new();
1015 options.insert("generations", GeneratorOption::Integer(1000));
1016 options.insert("width", GeneratorOption::Integer(1001));
1017
1018 let error = generate("cellular_automaton", &options)
1019 .expect_err("oversized cellular automaton matrix should fail before allocation");
1020
1021 assert!(matches!(
1022 error,
1023 PulseError::InvalidGeneratorOption { ref name, .. } if name == "generations*width"
1024 ));
1025 }
1026
1027 #[test]
1028 fn f32_options_reject_numbers_larger_than_f32() {
1029 let mut options = GeneratorOptions::new();
1030 options.insert("start", GeneratorOption::Number(1.0e100));
1031
1032 let error = options
1033 .f32_or("start", 0.0)
1034 .expect_err("oversized numeric value should not become infinity");
1035
1036 assert!(matches!(
1037 error,
1038 PulseError::InvalidGeneratorOption { ref name, .. } if name == "start"
1039 ));
1040 }
1041}