Skip to main content

mlua_pulse/
generators.rs

1//! Algorithmic sequence generator catalog and wrappers.
2
3use 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/// Metadata for one Lua-visible algorithm generator.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct GeneratorInfo {
13    /// Canonical generator name accepted by `pulse.generate`.
14    pub name: &'static str,
15    /// Generator family, such as mathematical, generative, musical, or rhythmic.
16    pub category: &'static str,
17    /// Lua-visible output shape.
18    pub output: &'static str,
19    /// Parameter names consumed by this generator.
20    pub parameters: &'static [&'static str],
21}
22
23/// Generated values that can be represented in Lua.
24#[derive(Debug, Clone, PartialEq)]
25pub enum PulseGenerated {
26    /// Floating point sequence.
27    Numbers(Vec<f32>),
28    /// Unsigned integer sequence.
29    Integers(Vec<u32>),
30    /// Zero-based step index sequence.
31    Steps(Vec<usize>),
32    /// Matrix of floating point values.
33    NumberMatrix(Vec<Vec<f32>>),
34    /// Matrix of unsigned integer values.
35    IntegerMatrix(Vec<Vec<u32>>),
36    /// Matrix of zero-based step indices.
37    StepMatrix(Vec<Vec<usize>>),
38    /// Generated text, used by L-system generators.
39    Text(String),
40}
41
42/// One typed option supplied by Lua.
43#[derive(Debug, Clone, PartialEq)]
44pub enum GeneratorOption {
45    /// Floating point option.
46    Number(f64),
47    /// Integer option.
48    Integer(i64),
49    /// Boolean option.
50    Boolean(bool),
51    /// Text option.
52    Text(String),
53    /// List of unsigned integers.
54    IntegerList(Vec<u32>),
55    /// Text map, currently used for L-system rewrite rules.
56    TextMap(BTreeMap<String, String>),
57}
58
59/// Generator options parsed from a Lua table.
60#[derive(Debug, Clone, Default, PartialEq)]
61pub struct GeneratorOptions {
62    values: BTreeMap<String, GeneratorOption>,
63}
64
65impl GeneratorOptions {
66    /// Creates an empty option set.
67    #[must_use]
68    pub fn new() -> Self {
69        Self::default()
70    }
71
72    /// Inserts or replaces one named option.
73    pub fn insert(&mut self, name: impl Into<String>, value: GeneratorOption) {
74        self.values.insert(name.into(), value);
75    }
76
77    /// Reads a required positive `usize` option.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error when the option is missing, non-integer, zero, or too large.
82    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    /// Reads an optional positive `usize` option.
93    ///
94    /// # Errors
95    ///
96    /// Returns an error when the supplied option is non-integer, zero, or too large.
97    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    /// Reads a required non-negative `u32` option.
105    ///
106    /// # Errors
107    ///
108    /// Returns an error when the option is missing, negative, non-integer, or too large.
109    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    /// Reads an optional non-negative `u32` option.
120    ///
121    /// # Errors
122    ///
123    /// Returns an error when the supplied option is negative, non-integer, or too large.
124    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    /// Reads an optional finite `f32` option.
132    ///
133    /// # Errors
134    ///
135    /// Returns an error when the supplied option is not numeric or not finite.
136    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    /// Reads a required finite `f32` option.
144    ///
145    /// # Errors
146    ///
147    /// Returns an error when the option is missing, not numeric, or not finite.
148    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    /// Reads an optional boolean option.
159    ///
160    /// # Errors
161    ///
162    /// Returns an error when the supplied option is not boolean.
163    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    /// Reads a required string option.
175    ///
176    /// # Errors
177    ///
178    /// Returns an error when the option is missing or not text.
179    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    /// Reads a required string-to-string map option.
193    ///
194    /// # Errors
195    ///
196    /// Returns an error when the option is missing or not a text map.
197    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/// Returns metadata for all Lua-visible generators.
395#[must_use]
396pub fn generator_infos() -> &'static [GeneratorInfo] {
397    GENERATOR_INFOS
398}
399
400/// Returns generator names in stable Lua API order.
401#[must_use]
402pub fn generator_names() -> Vec<&'static str> {
403    generator_infos().iter().map(|info| info.name).collect()
404}
405
406/// Looks up one generator's metadata.
407///
408/// # Errors
409///
410/// Returns `PulseError::InvalidGenerator` when `name` is not registered.
411pub 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
420/// Generates values for a registered generator name.
421///
422/// # Errors
423///
424/// Returns a generator error when the name is unknown or supplied options are
425/// missing or invalid.
426pub 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
711/// Normalizes finite values into the requested floating point range.
712///
713/// # Errors
714///
715/// Returns an invalid generator option error when the input is empty, contains
716/// non-finite values, or the target range is invalid.
717pub 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
729/// Maps finite values to equal-tempered frequencies in a named scale.
730///
731/// # Errors
732///
733/// Returns a generator option error for invalid values, scales, roots, or octave
734/// ranges.
735pub 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
882/// Ensures the product of two dimensions stays renderable.
883///
884/// Matrix generators such as cellular automata allocate one entry per cell, so a
885/// large `a * b` product can balloon memory even when each dimension is
886/// individually within limits. This rejects the product before allocation.
887///
888/// # Errors
889///
890/// Returns an error when `a * b` overflows or exceeds the sequence length limit.
891fn 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}