sim_lib_music_transform/mutator/
wire.rs1use 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}