use crate::error::{PulseError, PulseResult};
use crate::theory;
use std::collections::{BTreeMap, HashMap};
use tunes::sequences::{generative, mathematical as math, musical, rhythmic};
const MAX_GENERATOR_SEQUENCE_LENGTH: usize = 1_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GeneratorInfo {
pub name: &'static str,
pub category: &'static str,
pub output: &'static str,
pub parameters: &'static [&'static str],
}
#[derive(Debug, Clone, PartialEq)]
pub enum PulseGenerated {
Numbers(Vec<f32>),
Integers(Vec<u32>),
Steps(Vec<usize>),
NumberMatrix(Vec<Vec<f32>>),
IntegerMatrix(Vec<Vec<u32>>),
StepMatrix(Vec<Vec<usize>>),
Text(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum GeneratorOption {
Number(f64),
Integer(i64),
Boolean(bool),
Text(String),
IntegerList(Vec<u32>),
TextMap(BTreeMap<String, String>),
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct GeneratorOptions {
values: BTreeMap<String, GeneratorOption>,
}
impl GeneratorOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, name: impl Into<String>, value: GeneratorOption) {
self.values.insert(name.into(), value);
}
pub fn required_usize(&self, name: &str) -> PulseResult<usize> {
let value = self
.values
.get(name)
.ok_or_else(|| PulseError::MissingGeneratorOption {
name: name.to_string(),
})?;
option_to_positive_usize(name, value)
}
pub fn usize_or(&self, name: &str, default: usize) -> PulseResult<usize> {
match self.values.get(name) {
Some(value) => option_to_positive_usize(name, value),
None => Ok(default),
}
}
pub fn required_u32(&self, name: &str) -> PulseResult<u32> {
let value = self
.values
.get(name)
.ok_or_else(|| PulseError::MissingGeneratorOption {
name: name.to_string(),
})?;
option_to_u32(name, value)
}
pub fn u32_or(&self, name: &str, default: u32) -> PulseResult<u32> {
match self.values.get(name) {
Some(value) => option_to_u32(name, value),
None => Ok(default),
}
}
pub fn f32_or(&self, name: &str, default: f32) -> PulseResult<f32> {
match self.values.get(name) {
Some(value) => option_to_f32(name, value),
None => Ok(default),
}
}
pub fn required_f32(&self, name: &str) -> PulseResult<f32> {
let value = self
.values
.get(name)
.ok_or_else(|| PulseError::MissingGeneratorOption {
name: name.to_string(),
})?;
option_to_f32(name, value)
}
pub fn bool_or(&self, name: &str, default: bool) -> PulseResult<bool> {
match self.values.get(name) {
Some(GeneratorOption::Boolean(value)) => Ok(*value),
Some(value) => Err(PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: option_value_to_string(value),
}),
None => Ok(default),
}
}
pub fn required_text(&self, name: &str) -> PulseResult<&str> {
match self.values.get(name) {
Some(GeneratorOption::Text(value)) => Ok(value),
Some(value) => Err(PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: option_value_to_string(value),
}),
None => Err(PulseError::MissingGeneratorOption {
name: name.to_string(),
}),
}
}
pub fn required_text_map(&self, name: &str) -> PulseResult<&BTreeMap<String, String>> {
match self.values.get(name) {
Some(GeneratorOption::TextMap(value)) => Ok(value),
Some(value) => Err(PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: option_value_to_string(value),
}),
None => Err(PulseError::MissingGeneratorOption {
name: name.to_string(),
}),
}
}
}
const LENGTH: &[&str] = &["length"];
const START_STEP_LENGTH: &[&str] = &["start", "step", "length"];
const START_RATIO_LENGTH: &[&str] = &["start", "ratio", "length"];
const START_TERMS: &[&str] = &["start", "max_terms"];
const CHAOS: &[&str] = &["r", "initial", "length"];
const TENT: &[&str] = &["mu", "initial", "length"];
const RANDOM_WALK: &[&str] = &["start", "step", "steps"];
const BOUNDED_WALK: &[&str] = &["start", "step", "min", "max", "steps"];
const VAN_DER_CORPUT: &[&str] = &["length", "base"];
const PERLIN: &[&str] = &["seed", "frequency", "octaves", "persistence", "length"];
const L_SYSTEM: &[&str] = &["axiom", "rules", "iterations"];
const L_SYSTEM_VALUES: &[&str] = &["pattern"];
const CELLULAR: &[&str] = &["rule", "width", "generations"];
const CANTOR_SET: &[&str] = &["iterations", "resolution"];
const FIFTHS: &[&str] = &["length", "start"];
const HARMONIC: &[&str] = &["fundamental", "length"];
const GOLDEN_SECTIONS: &[&str] = &["value", "divisions"];
const EUCLIDEAN: &[&str] = &["pulses", "steps"];
const SHEPARD: &[&str] = &["length", "steps_per_octave", "ascending"];
const CIRCLE_MAP: &[&str] = &["omega", "k", "initial", "iterations"];
const GENERATOR_INFOS: &[GeneratorInfo] = &[
info("fibonacci", "mathematical", "integers", LENGTH),
info("fibonacci_short", "mathematical", "integers", &[]),
info("fibonacci_medium", "mathematical", "integers", &[]),
info("fibonacci_classic", "mathematical", "integers", &[]),
info("fibonacci_long", "mathematical", "integers", &[]),
info("fibonacci_extended", "mathematical", "integers", &[]),
info("primes", "mathematical", "integers", LENGTH),
info("primes_short", "mathematical", "integers", &[]),
info("primes_medium", "mathematical", "integers", &[]),
info("primes_classic", "mathematical", "integers", &[]),
info("primes_long", "mathematical", "integers", &[]),
info("primes_polyrhythm", "mathematical", "integers", &[]),
info("arithmetic", "mathematical", "integers", START_STEP_LENGTH),
info("arithmetic_ascending", "mathematical", "integers", &[]),
info("arithmetic_evens", "mathematical", "integers", &[]),
info("arithmetic_odds", "mathematical", "integers", &[]),
info("arithmetic_fives", "mathematical", "integers", &[]),
info("arithmetic_whole_tone", "mathematical", "integers", &[]),
info("arithmetic_chromatic", "mathematical", "integers", &[]),
info("geometric", "mathematical", "integers", START_RATIO_LENGTH),
info("geometric_doubling", "mathematical", "integers", &[]),
info("geometric_tripling", "mathematical", "integers", &[]),
info("geometric_exponential", "mathematical", "integers", &[]),
info("geometric_moderate", "mathematical", "integers", &[]),
info("geometric_aggressive", "mathematical", "integers", &[]),
info("triangular", "mathematical", "integers", LENGTH),
info("triangular_short", "mathematical", "integers", &[]),
info("triangular_classic", "mathematical", "integers", &[]),
info("triangular_extended", "mathematical", "integers", &[]),
info("powers_of_two", "mathematical", "integers", LENGTH),
info("powers_of_two_short", "mathematical", "integers", &[]),
info("powers_of_two_classic", "mathematical", "integers", &[]),
info("powers_of_two_extended", "mathematical", "integers", &[]),
info("powers_of_two_byte", "mathematical", "integers", &[]),
info("collatz", "mathematical", "integers", START_TERMS),
info("collatz_short", "mathematical", "integers", &[]),
info("collatz_medium", "mathematical", "integers", &[]),
info("collatz_classic", "mathematical", "integers", &[]),
info("collatz_dramatic", "mathematical", "integers", &[]),
info("collatz_epic", "mathematical", "integers", &[]),
info("collatz_sparse", "mathematical", "integers", &[]),
info("lucas", "mathematical", "integers", LENGTH),
info("lucas_short", "mathematical", "integers", &[]),
info("lucas_classic", "mathematical", "integers", &[]),
info("lucas_extended", "mathematical", "integers", &[]),
info("catalan", "mathematical", "integers", LENGTH),
info("catalan_short", "mathematical", "integers", &[]),
info("catalan_classic", "mathematical", "integers", &[]),
info("catalan_extended", "mathematical", "integers", &[]),
info("padovan", "mathematical", "integers", LENGTH),
info("padovan_short", "mathematical", "integers", &[]),
info("padovan_classic", "mathematical", "integers", &[]),
info("padovan_extended", "mathematical", "integers", &[]),
info("pell", "mathematical", "integers", LENGTH),
info("pell_short", "mathematical", "integers", &[]),
info("pell_classic", "mathematical", "integers", &[]),
info("pell_extended", "mathematical", "integers", &[]),
info("pentagonal", "mathematical", "integers", LENGTH),
info("pentagonal_short", "mathematical", "integers", &[]),
info("pentagonal_classic", "mathematical", "integers", &[]),
info("pentagonal_extended", "mathematical", "integers", &[]),
info("random_walk", "generative", "numbers", RANDOM_WALK),
info("random_walk_short", "generative", "numbers", &[]),
info("random_walk_classic", "generative", "numbers", &[]),
info("random_walk_long", "generative", "numbers", &[]),
info("random_walk_large_steps", "generative", "numbers", &[]),
info("bounded_walk", "generative", "numbers", BOUNDED_WALK),
info("bounded_walk_classic", "generative", "numbers", &[]),
info("bounded_walk_narrow", "generative", "numbers", &[]),
info("bounded_walk_wide", "generative", "numbers", &[]),
info("logistic_map", "generative", "numbers", CHAOS),
info("logistic_ordered", "generative", "numbers", &[]),
info("logistic_oscillating", "generative", "numbers", &[]),
info("logistic_edge_of_chaos", "generative", "numbers", &[]),
info("logistic_chaotic", "generative", "numbers", &[]),
info("logistic_classic", "generative", "numbers", &[]),
info("tent_map", "generative", "numbers", TENT),
info("tent_chaotic", "generative", "numbers", LENGTH),
info("tent_moderate", "generative", "numbers", LENGTH),
info("tent_mild", "generative", "numbers", LENGTH),
info("sine_map", "generative", "numbers", CHAOS),
info("sine_smooth", "generative", "numbers", LENGTH),
info("sine_chaotic", "generative", "numbers", LENGTH),
info("sine_gentle", "generative", "numbers", LENGTH),
info("thue_morse", "generative", "integers", LENGTH),
info("thue_morse_short", "generative", "integers", &[]),
info("thue_morse_classic", "generative", "integers", &[]),
info("thue_morse_extended", "generative", "integers", &[]),
info("recaman", "generative", "integers", LENGTH),
info("recaman_short", "generative", "integers", &[]),
info("recaman_classic", "generative", "integers", &[]),
info("recaman_long", "generative", "integers", &[]),
info("recaman_epic", "generative", "integers", &[]),
info("van_der_corput", "generative", "numbers", VAN_DER_CORPUT),
info("van_der_corput_short", "generative", "numbers", &[]),
info("van_der_corput_classic", "generative", "numbers", &[]),
info("van_der_corput_long", "generative", "numbers", &[]),
info("van_der_corput_ternary", "generative", "numbers", &[]),
info("van_der_corput_pentary", "generative", "numbers", &[]),
info("perlin_noise", "generative", "numbers", PERLIN),
info("perlin_smooth_drift", "generative", "numbers", &[]),
info("perlin_classic", "generative", "numbers", &[]),
info("perlin_rich_texture", "generative", "numbers", &[]),
info("perlin_fast_modulation", "generative", "numbers", &[]),
info("perlin_ambient_pad", "generative", "numbers", &[]),
info("l_system", "generative", "text", L_SYSTEM),
info("l_system_values", "generative", "integers", L_SYSTEM_VALUES),
info("l_system_fibonacci", "generative", "text", &[]),
info("l_system_thue_morse", "generative", "text", &[]),
info("l_system_cantor", "generative", "text", &[]),
info("l_system_binary_tree", "generative", "text", &[]),
info("l_system_classic", "generative", "text", &[]),
info(
"cellular_automaton",
"generative",
"integer_matrix",
CELLULAR,
),
info("cellular_rule30", "generative", "integer_matrix", &[]),
info("cellular_rule110", "generative", "integer_matrix", &[]),
info("cellular_rule90", "generative", "integer_matrix", &[]),
info("cellular_rule184", "generative", "integer_matrix", &[]),
info("cellular_classic", "generative", "integer_matrix", &[]),
info("cantor_set", "generative", "integers", CANTOR_SET),
info("cantor_rhythm_16", "generative", "steps", &[]),
info("cantor_rhythm_27", "generative", "steps", &[]),
info("cantor_rhythm_9", "generative", "steps", &[]),
info("cantor_rhythm_81", "generative", "steps", &[]),
info("markov", "generative", "integers", &[]),
info("markov_c_major_melody", "generative", "integers", &[]),
info("markov_up_down", "generative", "integers", &[]),
info("circle_of_fifths", "musical", "integers", FIFTHS),
info("circle_of_fourths", "musical", "integers", FIFTHS),
info("harmonic_series", "musical", "numbers", HARMONIC),
info("undertone_series", "musical", "numbers", LENGTH),
info("golden_ratio", "musical", "numbers", LENGTH),
info("golden_sections", "musical", "numbers", GOLDEN_SECTIONS),
info("pythagorean_tuning", "musical", "numbers", LENGTH),
info("just_intonation_major", "musical", "numbers", &[]),
info("just_intonation_minor", "musical", "numbers", &[]),
info("euclidean", "rhythmic", "steps", EUCLIDEAN),
info("euclidean_pattern", "rhythmic", "integers", EUCLIDEAN),
info("golden_ratio_rhythm", "rhythmic", "steps", &["steps"]),
info("shepard_tone", "rhythmic", "integers", SHEPARD),
info("circle_map", "rhythmic", "numbers", CIRCLE_MAP),
];
const fn info(
name: &'static str,
category: &'static str,
output: &'static str,
parameters: &'static [&'static str],
) -> GeneratorInfo {
GeneratorInfo {
name,
category,
output,
parameters,
}
}
#[must_use]
pub fn generator_infos() -> &'static [GeneratorInfo] {
GENERATOR_INFOS
}
#[must_use]
pub fn generator_names() -> Vec<&'static str> {
generator_infos().iter().map(|info| info.name).collect()
}
pub fn generator_info(name: &str) -> PulseResult<&'static GeneratorInfo> {
generator_infos()
.iter()
.find(|info| info.name == name)
.ok_or_else(|| PulseError::InvalidGenerator {
name: name.to_string(),
})
}
pub fn generate(name: &str, options: &GeneratorOptions) -> PulseResult<PulseGenerated> {
match name {
"fibonacci" => Ok(integers(math::fibonacci::generate(
options.required_usize("length")?,
))),
"fibonacci_short" => Ok(integers(math::fibonacci::short())),
"fibonacci_medium" => Ok(integers(math::fibonacci::medium())),
"fibonacci_classic" => Ok(integers(math::fibonacci::classic())),
"fibonacci_long" => Ok(integers(math::fibonacci::long())),
"fibonacci_extended" => Ok(integers(math::fibonacci::extended())),
"primes" => Ok(integers(math::primes::generate(
options.required_usize("length")?,
))),
"primes_short" => Ok(integers(math::primes::short())),
"primes_medium" => Ok(integers(math::primes::medium())),
"primes_classic" => Ok(integers(math::primes::classic())),
"primes_long" => Ok(integers(math::primes::long())),
"primes_polyrhythm" => Ok(integers(math::primes::polyrhythm())),
"arithmetic" => Ok(integers(math::arithmetic::generate(
options.u32_or("start", 0)?,
options.u32_or("step", 1)?,
options.required_usize("length")?,
))),
"arithmetic_ascending" => Ok(integers(math::arithmetic::ascending())),
"arithmetic_evens" => Ok(integers(math::arithmetic::evens())),
"arithmetic_odds" => Ok(integers(math::arithmetic::odds())),
"arithmetic_fives" => Ok(integers(math::arithmetic::fives())),
"arithmetic_whole_tone" => Ok(integers(math::arithmetic::whole_tone())),
"arithmetic_chromatic" => Ok(integers(math::arithmetic::chromatic())),
"geometric" => Ok(integers(math::geometric::generate(
options.u32_or("start", 1)?,
options.u32_or("ratio", 2)?,
options.required_usize("length")?,
))),
"geometric_doubling" => Ok(integers(math::geometric::doubling())),
"geometric_tripling" => Ok(integers(math::geometric::tripling())),
"geometric_exponential" => Ok(integers(math::geometric::exponential())),
"geometric_moderate" => Ok(integers(math::geometric::moderate())),
"geometric_aggressive" => Ok(integers(math::geometric::aggressive())),
"triangular" => Ok(integers(math::triangular::generate(
options.required_usize("length")?,
))),
"triangular_short" => Ok(integers(math::triangular::short())),
"triangular_classic" => Ok(integers(math::triangular::classic())),
"triangular_extended" => Ok(integers(math::triangular::extended())),
"powers_of_two" => Ok(integers(math::powers_of_two::generate(
options.required_usize("length")?,
))),
"powers_of_two_short" => Ok(integers(math::powers_of_two::short())),
"powers_of_two_classic" => Ok(integers(math::powers_of_two::classic())),
"powers_of_two_extended" => Ok(integers(math::powers_of_two::extended())),
"powers_of_two_byte" => Ok(integers(math::powers_of_two::byte())),
"collatz" => Ok(integers(math::collatz::generate(
options.u32_or("start", 27)?,
options.required_usize("max_terms")?,
))),
"collatz_short" => Ok(integers(math::collatz::short())),
"collatz_medium" => Ok(integers(math::collatz::medium())),
"collatz_classic" => Ok(integers(math::collatz::classic())),
"collatz_dramatic" => Ok(integers(math::collatz::dramatic())),
"collatz_epic" => Ok(integers(math::collatz::epic())),
"collatz_sparse" => Ok(integers(math::collatz::sparse())),
"lucas" => Ok(integers(math::lucas::generate(
options.required_usize("length")?,
))),
"lucas_short" => Ok(integers(math::lucas::short())),
"lucas_classic" => Ok(integers(math::lucas::classic())),
"lucas_extended" => Ok(integers(math::lucas::extended())),
"catalan" => Ok(integers(math::catalan::generate(
options.required_usize("length")?,
))),
"catalan_short" => Ok(integers(math::catalan::short())),
"catalan_classic" => Ok(integers(math::catalan::classic())),
"catalan_extended" => Ok(integers(math::catalan::extended())),
"padovan" => Ok(integers(math::padovan::generate(
options.required_usize("length")?,
))),
"padovan_short" => Ok(integers(math::padovan::short())),
"padovan_classic" => Ok(integers(math::padovan::classic())),
"padovan_extended" => Ok(integers(math::padovan::extended())),
"pell" => Ok(integers(math::pell::generate(
options.required_usize("length")?,
))),
"pell_short" => Ok(integers(math::pell::short())),
"pell_classic" => Ok(integers(math::pell::classic())),
"pell_extended" => Ok(integers(math::pell::extended())),
"pentagonal" => Ok(integers(math::pentagonal::generate(
options.required_usize("length")?,
))),
"pentagonal_short" => Ok(integers(math::pentagonal::short())),
"pentagonal_classic" => Ok(integers(math::pentagonal::classic())),
"pentagonal_extended" => Ok(integers(math::pentagonal::extended())),
"random_walk" => Ok(numbers(generative::random_walk::generate(
options.f32_or("start", 0.0)?,
options.f32_or("step", 1.0)?,
options.required_usize("steps")?,
))),
"random_walk_short" => Ok(numbers(generative::random_walk::short())),
"random_walk_classic" => Ok(numbers(generative::random_walk::classic())),
"random_walk_long" => Ok(numbers(generative::random_walk::long())),
"random_walk_large_steps" => Ok(numbers(generative::random_walk::large_steps())),
"bounded_walk" => Ok(numbers(generative::bounded_walk::generate(
options.f32_or("start", 0.0)?,
options.f32_or("step", 1.0)?,
options.f32_or("min", -12.0)?,
options.f32_or("max", 12.0)?,
options.required_usize("steps")?,
))),
"bounded_walk_classic" => Ok(numbers(generative::bounded_walk::classic())),
"bounded_walk_narrow" => Ok(numbers(generative::bounded_walk::narrow())),
"bounded_walk_wide" => Ok(numbers(generative::bounded_walk::wide())),
"logistic_map" => Ok(numbers(generative::logistic_map::generate(
options.f32_or("r", 3.9)?,
options.f32_or("initial", 0.5)?,
options.required_usize("length")?,
))),
"logistic_ordered" => Ok(numbers(generative::logistic_map::ordered())),
"logistic_oscillating" => Ok(numbers(generative::logistic_map::oscillating())),
"logistic_edge_of_chaos" => Ok(numbers(generative::logistic_map::edge_of_chaos())),
"logistic_chaotic" => Ok(numbers(generative::logistic_map::chaotic())),
"logistic_classic" => Ok(numbers(generative::logistic_map::classic())),
"tent_map" => Ok(numbers(generative::tent_map::generate(
options.f32_or("mu", 2.0)?,
options.f32_or("initial", 0.5)?,
options.required_usize("length")?,
))),
"tent_chaotic" => Ok(numbers(generative::tent_map::tent_chaotic(
options.required_usize("length")?,
))),
"tent_moderate" => Ok(numbers(generative::tent_map::tent_moderate(
options.required_usize("length")?,
))),
"tent_mild" => Ok(numbers(generative::tent_map::tent_mild(
options.required_usize("length")?,
))),
"sine_map" => Ok(numbers(generative::sine_map::generate(
options.f32_or("r", 0.9)?,
options.f32_or("initial", 0.5)?,
options.required_usize("length")?,
))),
"sine_smooth" => Ok(numbers(generative::sine_map::sine_smooth(
options.required_usize("length")?,
))),
"sine_chaotic" => Ok(numbers(generative::sine_map::sine_chaotic(
options.required_usize("length")?,
))),
"sine_gentle" => Ok(numbers(generative::sine_map::sine_gentle(
options.required_usize("length")?,
))),
"thue_morse" => Ok(integers(generative::thue_morse::generate(
options.required_usize("length")?,
))),
"thue_morse_short" => Ok(integers(generative::thue_morse::short())),
"thue_morse_classic" => Ok(integers(generative::thue_morse::classic())),
"thue_morse_extended" => Ok(integers(generative::thue_morse::extended())),
"recaman" => Ok(integers(generative::recaman::generate(
options.required_usize("length")?,
))),
"recaman_short" => Ok(integers(generative::recaman::short())),
"recaman_classic" => Ok(integers(generative::recaman::classic())),
"recaman_long" => Ok(integers(generative::recaman::long())),
"recaman_epic" => Ok(integers(generative::recaman::epic())),
"van_der_corput" => Ok(numbers(generative::van_der_corput::generate(
options.required_usize("length")?,
options.u32_or("base", 2)?,
))),
"van_der_corput_short" => Ok(numbers(generative::van_der_corput::short())),
"van_der_corput_classic" => Ok(numbers(generative::van_der_corput::classic())),
"van_der_corput_long" => Ok(numbers(generative::van_der_corput::long())),
"van_der_corput_ternary" => Ok(numbers(generative::van_der_corput::ternary())),
"van_der_corput_pentary" => Ok(numbers(generative::van_der_corput::pentary())),
"perlin_noise" => Ok(numbers(generative::perlin_noise::generate(
options.u32_or("seed", 42)?,
options.f32_or("frequency", 0.1)?,
options.u32_or("octaves", 3)?,
options.f32_or("persistence", 0.5)?,
options.required_usize("length")?,
))),
"perlin_smooth_drift" => Ok(numbers(generative::perlin_noise::smooth_drift())),
"perlin_classic" => Ok(numbers(generative::perlin_noise::classic())),
"perlin_rich_texture" => Ok(numbers(generative::perlin_noise::rich_texture())),
"perlin_fast_modulation" => Ok(numbers(generative::perlin_noise::fast_modulation())),
"perlin_ambient_pad" => Ok(numbers(generative::perlin_noise::ambient_pad())),
"l_system" => Ok(PulseGenerated::Text(generative::lsystem::generate(
options.required_text("axiom")?,
&lsystem_rules(options)?,
options.required_usize("iterations")?,
))),
"l_system_values" => Ok(integers(generative::lsystem::lsystem_to_sequence(
options.required_text("pattern")?,
))),
"l_system_fibonacci" => Ok(PulseGenerated::Text(generative::lsystem::fibonacci())),
"l_system_thue_morse" => Ok(PulseGenerated::Text(generative::lsystem::thue_morse())),
"l_system_cantor" => Ok(PulseGenerated::Text(generative::lsystem::cantor())),
"l_system_binary_tree" => Ok(PulseGenerated::Text(generative::lsystem::binary_tree())),
"l_system_classic" => Ok(PulseGenerated::Text(generative::lsystem::classic())),
"cellular_automaton" => {
let generations = options.required_usize("generations")?;
let width = options.required_usize("width")?;
assert_product_within_sequence_limit("generations*width", generations, width)?;
Ok(integer_matrix(generative::cellular_automaton::generate(
option_u8_or(options, "rule", 30)?,
generations,
width,
None,
)))
}
"cellular_rule30" => Ok(integer_matrix(generative::cellular_automaton::rule30())),
"cellular_rule110" => Ok(integer_matrix(generative::cellular_automaton::rule110())),
"cellular_rule90" => Ok(integer_matrix(generative::cellular_automaton::rule90())),
"cellular_rule184" => Ok(integer_matrix(generative::cellular_automaton::rule184())),
"cellular_classic" => Ok(integer_matrix(generative::cellular_automaton::classic())),
"cantor_set" => Ok(integers(generative::cantor_set::generate(
options.required_usize("iterations")?,
options.required_usize("resolution")?,
))),
"cantor_rhythm_16" => Ok(steps(generative::cantor_set::cantor_rhythm_16())),
"cantor_rhythm_27" => Ok(steps(generative::cantor_set::cantor_rhythm_27())),
"cantor_rhythm_9" => Ok(steps(generative::cantor_set::cantor_rhythm_9())),
"cantor_rhythm_81" => Ok(steps(generative::cantor_set::cantor_rhythm_81())),
"markov" | "markov_c_major_melody" => Ok(integers(generative::markov::c_major_melody())),
"markov_up_down" => Ok(integers(generative::markov::up_down())),
"circle_of_fifths" => Ok(integers_from_u8(
musical::circle_of_fifths::circle_of_fifths(
options.required_usize("length")?,
option_u8_or(options, "start", 0)?,
),
)),
"circle_of_fourths" => Ok(integers_from_u8(
musical::circle_of_fifths::circle_of_fourths(
options.required_usize("length")?,
option_u8_or(options, "start", 0)?,
),
)),
"harmonic_series" => Ok(numbers(musical::harmonic_series::harmonic_series(
options.required_f32("fundamental")?,
options.required_usize("length")?,
))),
"undertone_series" => Ok(numbers(musical::undertone_series::undertone_series(
options.required_usize("length")?,
))),
"golden_ratio" => Ok(numbers(musical::golden_ratio::golden_ratio(
options.required_usize("length")?,
))),
"golden_sections" => Ok(numbers(musical::golden_sections::golden_sections(
options.required_f32("value")?,
options.required_usize("divisions")?,
))),
"pythagorean_tuning" => Ok(numbers(musical::pythagorean_tuning::pythagorean_tuning(
options.required_usize("length")?,
))),
"just_intonation_major" => {
Ok(numbers(musical::pythagorean_tuning::just_intonation_major()))
}
"just_intonation_minor" => {
Ok(numbers(musical::pythagorean_tuning::just_intonation_minor()))
}
"euclidean" => Ok(steps(rhythmic::euclidean::generate(
options.required_usize("pulses")?,
options.required_usize("steps")?,
))),
"euclidean_pattern" => Ok(integers(rhythmic::euclidean::pattern(
options.required_usize("pulses")?,
options.required_usize("steps")?,
))),
"golden_ratio_rhythm" => Ok(steps(rhythmic::golden_ratio_rhythm::generate(
options.required_usize("steps")?,
))),
"shepard_tone" => Ok(integers(rhythmic::shepard_tone::generate(
options.required_usize("length")?,
options.u32_or("steps_per_octave", 12)?,
options.bool_or("ascending", true)?,
))),
"circle_map" => Ok(numbers(rhythmic::circle_map::generate(
options.f32_or("omega", 0.618_034)?,
options.f32_or("k", 1.5)?,
options.f32_or("initial", 0.0)?,
options.required_usize("iterations")?,
))),
_ => Err(PulseError::InvalidGenerator {
name: name.to_string(),
}),
}
}
pub fn normalize_values(values: &[f32], min: f32, max: f32) -> PulseResult<Vec<f32>> {
validate_value_slice("values", values)?;
if !min.is_finite() || !max.is_finite() || min >= max {
return Err(PulseError::InvalidGeneratorOption {
name: "range".to_string(),
value: format!("{min}..{max}"),
});
}
Ok(musical::normalize::normalize_f32(values, min, max))
}
pub fn map_values_to_scale(
values: &[f32],
scale: &str,
root: &str,
octaves: u32,
) -> PulseResult<Vec<f32>> {
validate_value_slice("values", values)?;
if octaves == 0 {
return Err(PulseError::InvalidGeneratorOption {
name: "octaves".to_string(),
value: octaves.to_string(),
});
}
let scale = scale_intervals(scale)?;
let root = theory::parse_note_frequency(root)?;
Ok(musical::map_to_scale::map_to_scale_f32(
values, &scale, root, octaves,
))
}
fn numbers(values: Vec<f32>) -> PulseGenerated {
PulseGenerated::Numbers(values)
}
fn integers(values: Vec<u32>) -> PulseGenerated {
PulseGenerated::Integers(values)
}
fn integers_from_u8(values: Vec<u8>) -> PulseGenerated {
PulseGenerated::Integers(values.into_iter().map(u32::from).collect())
}
fn steps(values: Vec<usize>) -> PulseGenerated {
PulseGenerated::Steps(values)
}
fn integer_matrix(values: Vec<Vec<u32>>) -> PulseGenerated {
PulseGenerated::IntegerMatrix(values)
}
fn lsystem_rules(options: &GeneratorOptions) -> PulseResult<HashMap<char, String>> {
let mut rules = HashMap::new();
for (key, value) in options.required_text_map("rules")? {
let mut chars = key.chars();
let Some(symbol) = chars.next() else {
return Err(PulseError::InvalidGeneratorOption {
name: "rules".to_string(),
value: key.clone(),
});
};
if chars.next().is_some() {
return Err(PulseError::InvalidGeneratorOption {
name: "rules".to_string(),
value: key.clone(),
});
}
rules.insert(symbol, value.clone());
}
Ok(rules)
}
fn option_u8_or(options: &GeneratorOptions, name: &str, default: u8) -> PulseResult<u8> {
let value = options.u32_or(name, u32::from(default))?;
u8::try_from(value).map_err(|_| PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: value.to_string(),
})
}
fn validate_value_slice(name: &str, values: &[f32]) -> PulseResult<()> {
if values.is_empty() {
return Err(PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: "empty".to_string(),
});
}
if values.iter().any(|value| !value.is_finite()) {
return Err(PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: "non-finite".to_string(),
});
}
Ok(())
}
fn scale_intervals(scale: &str) -> PulseResult<Vec<u32>> {
let normalized = scale.to_ascii_lowercase();
let intervals = match normalized.as_str() {
"major" | "ionian" => musical::map_to_scale::Scale::major(),
"minor" | "aeolian" => musical::map_to_scale::Scale::minor(),
"major_pentatonic" => musical::map_to_scale::Scale::major_pentatonic(),
"minor_pentatonic" => musical::map_to_scale::Scale::minor_pentatonic(),
"harmonic_minor" => musical::map_to_scale::Scale::harmonic_minor(),
"blues" => musical::map_to_scale::Scale::blues(),
"chromatic" => musical::map_to_scale::Scale::chromatic(),
"whole_tone" => musical::map_to_scale::Scale::whole_tone(),
"dorian" => musical::map_to_scale::Scale::dorian(),
"phrygian" => musical::map_to_scale::Scale::phrygian(),
"lydian" => musical::map_to_scale::Scale::lydian(),
"mixolydian" => musical::map_to_scale::Scale::mixolydian(),
_ => {
return Err(PulseError::InvalidGeneratorOption {
name: "scale".to_string(),
value: scale.to_string(),
});
}
};
Ok(intervals)
}
fn option_to_positive_usize(name: &str, value: &GeneratorOption) -> PulseResult<usize> {
let parsed = match value {
GeneratorOption::Integer(value) if *value > 0 => usize::try_from(*value)
.ok()
.filter(|value| *value <= MAX_GENERATOR_SEQUENCE_LENGTH),
GeneratorOption::Number(value)
if value.is_finite()
&& *value > 0.0
&& *value <= MAX_GENERATOR_SEQUENCE_LENGTH as f64
&& *value < usize_upper_bound_as_f64()
&& value.fract() == 0.0 =>
{
usize::try_from(*value as u64).ok()
}
_ => None,
};
parsed.ok_or_else(|| PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: option_value_to_string(value),
})
}
fn usize_upper_bound_as_f64() -> f64 {
let upper = usize::MAX as f64;
if (upper as u128) > usize::MAX as u128 {
upper
} else {
upper + 1.0
}
}
fn assert_product_within_sequence_limit(name: &str, a: usize, b: usize) -> PulseResult<()> {
a.checked_mul(b)
.filter(|&product| product <= MAX_GENERATOR_SEQUENCE_LENGTH)
.ok_or_else(|| PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: format!("{a}*{b}"),
})?;
Ok(())
}
fn option_to_u32(name: &str, value: &GeneratorOption) -> PulseResult<u32> {
let parsed = match value {
GeneratorOption::Integer(value) if *value >= 0 => u32::try_from(*value).ok(),
GeneratorOption::Number(value)
if value.is_finite()
&& *value >= 0.0
&& *value <= f64::from(u32::MAX)
&& value.fract() == 0.0 =>
{
u32::try_from(*value as u64).ok()
}
_ => None,
};
parsed.ok_or_else(|| PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: option_value_to_string(value),
})
}
fn option_to_f32(name: &str, value: &GeneratorOption) -> PulseResult<f32> {
let parsed = match value {
GeneratorOption::Integer(value) => Some(*value as f32),
GeneratorOption::Number(value)
if value.is_finite()
&& *value >= f64::from(f32::MIN)
&& *value <= f64::from(f32::MAX) =>
{
Some(*value as f32)
}
_ => None,
};
parsed.ok_or_else(|| PulseError::InvalidGeneratorOption {
name: name.to_string(),
value: option_value_to_string(value),
})
}
fn option_value_to_string(value: &GeneratorOption) -> String {
match value {
GeneratorOption::Number(value) => value.to_string(),
GeneratorOption::Integer(value) => value.to_string(),
GeneratorOption::Boolean(value) => value.to_string(),
GeneratorOption::Text(value) => value.clone(),
GeneratorOption::IntegerList(values) => format!("{values:?}"),
GeneratorOption::TextMap(values) => format!("{values:?}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_has_required_names_and_enough_entries() {
let names = generator_names();
assert!(names.len() >= 50);
assert!(names.contains(&"fibonacci"));
assert!(names.contains(&"primes"));
assert!(names.contains(&"l_system"));
assert!(names.contains(&"cellular_automaton"));
assert!(names.contains(&"logistic_map"));
assert!(names.contains(&"cantor_rhythm_16"));
}
#[test]
fn generates_basic_sequences() {
let mut options = GeneratorOptions::new();
options.insert("length", GeneratorOption::Integer(8));
assert_eq!(
generate("fibonacci", &options).unwrap(),
PulseGenerated::Integers(vec![1, 1, 2, 3, 5, 8, 13, 21])
);
assert_eq!(
generate("primes", &options).unwrap(),
PulseGenerated::Integers(vec![2, 3, 5, 7, 11, 13, 17, 19])
);
}
#[test]
fn usize_options_reject_numbers_larger_than_usize() {
let mut options = GeneratorOptions::new();
options.insert("length", GeneratorOption::Number(1.0e100));
let error = options
.required_usize("length")
.expect_err("oversized numeric length should fail before generation");
assert!(matches!(
error,
PulseError::InvalidGeneratorOption { ref name, .. } if name == "length"
));
}
#[test]
fn usize_options_reject_unrenderable_sequence_lengths() {
let mut options = GeneratorOptions::new();
options.insert("length", GeneratorOption::Integer(1_000_001));
let error = options
.required_usize("length")
.expect_err("oversized generator length should fail before allocation");
assert!(matches!(
error,
PulseError::InvalidGeneratorOption { ref name, .. } if name == "length"
));
}
#[test]
fn cellular_automaton_rejects_unrenderable_matrix_size() {
let mut options = GeneratorOptions::new();
options.insert("generations", GeneratorOption::Integer(1000));
options.insert("width", GeneratorOption::Integer(1001));
let error = generate("cellular_automaton", &options)
.expect_err("oversized cellular automaton matrix should fail before allocation");
assert!(matches!(
error,
PulseError::InvalidGeneratorOption { ref name, .. } if name == "generations*width"
));
}
#[test]
fn f32_options_reject_numbers_larger_than_f32() {
let mut options = GeneratorOptions::new();
options.insert("start", GeneratorOption::Number(1.0e100));
let error = options
.f32_or("start", 0.0)
.expect_err("oversized numeric value should not become infinity");
assert!(matches!(
error,
PulseError::InvalidGeneratorOption { ref name, .. } if name == "start"
));
}
}