Skip to main content

sim_lib_music_transform/mutator/
wire.rs

1use std::num::ParseIntError;
2
3use sim_lib_music_core::Time;
4use sim_lib_pitch_core::{Pitch, PitchClass};
5use sim_lib_pitch_scale::{Mode, Scale};
6
7use super::{MutationOp, PatternMutatorError};
8
9pub(super) fn op_wire(op: &MutationOp) -> String {
10    match op {
11        MutationOp::Reverse => "reverse".to_owned(),
12        MutationOp::Rotate { steps } => format!("rotate:{steps}"),
13        MutationOp::Transpose { semitones } => format!("transpose:{semitones}"),
14        MutationOp::Invert { axis } => format!("invert:{}", axis.semitone()),
15        MutationOp::ShuffleWithinBeat { beat } => format!("shuffle:{}", time_wire(*beat)),
16        MutationOp::Thin { keep_percent } => format!("thin:{keep_percent}"),
17        MutationOp::Thicken { semitones } => format!("thicken:{semitones}"),
18        MutationOp::VelocityRemap { low, high } => format!("velocity:{low}:{high}"),
19        MutationOp::RhythmDisplace { offset } => format!("rhythm:{}", time_wire(*offset)),
20        MutationOp::ScaleConform { scale } => {
21            format!("scale:{}:{}", scale.tonic.value(), scale.mode.name())
22        }
23    }
24}
25
26pub(super) fn parse_op(value: &str) -> Result<MutationOp, PatternMutatorError> {
27    let mut parts = value.split(':');
28    match parts.next().ok_or(PatternMutatorError::InvalidWire)? {
29        "reverse" => Ok(MutationOp::Reverse),
30        "rotate" => Ok(MutationOp::Rotate {
31            steps: parse_required(parts.next())?,
32        }),
33        "transpose" => Ok(MutationOp::Transpose {
34            semitones: parse_required(parts.next())?,
35        }),
36        "invert" => Ok(MutationOp::Invert {
37            axis: Pitch::from_semitone(parse_required(parts.next())?),
38        }),
39        "shuffle" => Ok(MutationOp::ShuffleWithinBeat {
40            beat: parse_time(parts.next().ok_or(PatternMutatorError::InvalidWire)?)?,
41        }),
42        "thin" => Ok(MutationOp::Thin {
43            keep_percent: parse_required(parts.next())?,
44        }),
45        "thicken" => Ok(MutationOp::Thicken {
46            semitones: parse_required(parts.next())?,
47        }),
48        "velocity" => Ok(MutationOp::VelocityRemap {
49            low: parse_required(parts.next())?,
50            high: parse_required(parts.next())?,
51        }),
52        "rhythm" => Ok(MutationOp::RhythmDisplace {
53            offset: parse_time(parts.next().ok_or(PatternMutatorError::InvalidWire)?)?,
54        }),
55        "scale" => {
56            let tonic = parse_required(parts.next())?;
57            let mode = parse_mode(parts.next().ok_or(PatternMutatorError::InvalidWire)?)?;
58            let tonic = PitchClass::new(tonic)
59                .map_err(|_| PatternMutatorError::InvalidPitchClass(tonic))?;
60            Ok(MutationOp::ScaleConform {
61                scale: Scale::new(tonic, mode),
62            })
63        }
64        _ => Err(PatternMutatorError::InvalidWire),
65    }
66}
67
68fn parse_required<T: std::str::FromStr>(value: Option<&str>) -> Result<T, PatternMutatorError>
69where
70    PatternMutatorError: From<<T as std::str::FromStr>::Err>,
71{
72    value
73        .ok_or(PatternMutatorError::InvalidWire)?
74        .parse()
75        .map_err(PatternMutatorError::from)
76}
77
78pub(super) fn parse_number<T: std::str::FromStr>(value: &str) -> Result<T, PatternMutatorError>
79where
80    PatternMutatorError: From<<T as std::str::FromStr>::Err>,
81{
82    value.parse().map_err(PatternMutatorError::from)
83}
84
85impl From<ParseIntError> for PatternMutatorError {
86    fn from(_: ParseIntError) -> Self {
87        Self::InvalidNumber
88    }
89}
90
91fn time_wire(time: Time) -> String {
92    format!("{}/{}", time.numer(), time.denom())
93}
94
95fn parse_time(value: &str) -> Result<Time, PatternMutatorError> {
96    let (numer, denom) = value
97        .split_once('/')
98        .ok_or(PatternMutatorError::InvalidWire)?;
99    Ok(Time::new(parse_number(numer)?, parse_number(denom)?))
100}
101
102fn parse_mode(value: &str) -> Result<Mode, PatternMutatorError> {
103    match value {
104        "major" => Ok(Mode::Major),
105        "minor-natural" => Ok(Mode::MinorNatural),
106        "minor-harmonic" => Ok(Mode::MinorHarmonic),
107        "minor-melodic" => Ok(Mode::MinorMelodic),
108        "dorian" => Ok(Mode::Dorian),
109        "phrygian" => Ok(Mode::Phrygian),
110        "lydian" => Ok(Mode::Lydian),
111        "mixolydian" => Ok(Mode::Mixolydian),
112        "aeolian" => Ok(Mode::Aeolian),
113        "locrian" => Ok(Mode::Locrian),
114        "whole-tone" => Ok(Mode::WholeTone),
115        "diminished" => Ok(Mode::Diminished),
116        "chromatic" => Ok(Mode::Chromatic),
117        _ => Err(PatternMutatorError::InvalidMode(value.to_owned())),
118    }
119}