Skip to main content

sim_lib_music_transform/
diagnostic.rs

1use sim_lib_music_core::Music;
2use sim_lib_pitch_core::Pitch;
3
4/// Classification of a problem a transform encountered while running.
5#[derive(Copy, Clone, Debug, PartialEq, Eq)]
6pub enum TransformDiagnosticCode {
7    /// An inversion axis could not be resolved to a concrete pitch.
8    InvalidAxis,
9    /// A remap matrix had a non-positive divisor or out-of-range output.
10    InvalidMatrix,
11    /// A frequency or time ratio was not positive.
12    InvalidRatio,
13    /// A time map was malformed (too few points or non-monotonic).
14    InvalidTimeMap,
15    /// A pitch lacked the MIDI key a drum-key remap requires.
16    MissingMidiKey,
17    /// A time map produced a negative note duration.
18    NonPositiveDuration,
19    /// A pitch class fell outside the scale a remap needs.
20    PitchOutOfScale,
21    /// A mapping was requested that the transform cannot perform.
22    UnsupportedMapping,
23}
24
25/// A single diagnostic emitted by a transform, tagged with its origin.
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct TransformDiagnostic {
28    /// Machine-readable classification of the problem.
29    pub code: TransformDiagnosticCode,
30    /// Name of the transform that produced the diagnostic.
31    pub transform: &'static str,
32    /// Human-readable description of the problem.
33    pub message: String,
34}
35
36impl TransformDiagnostic {
37    /// Builds a diagnostic from its code, originating transform, and message.
38    pub fn new(
39        code: TransformDiagnosticCode,
40        transform: &'static str,
41        message: impl Into<String>,
42    ) -> Self {
43        Self {
44            code,
45            transform,
46            message: message.into(),
47        }
48    }
49}
50
51/// Result of a transform: the produced music plus any diagnostics raised.
52#[derive(Clone, Debug)]
53pub struct TransformReport {
54    /// The transformed musical material.
55    pub music: Music,
56    /// Diagnostics gathered while producing [`Self::music`].
57    pub diagnostics: Vec<TransformDiagnostic>,
58}
59
60impl TransformReport {
61    /// Wraps music with no diagnostics.
62    pub fn clean(music: Music) -> Self {
63        Self {
64            music,
65            diagnostics: Vec::new(),
66        }
67    }
68
69    /// Wraps music with a single diagnostic.
70    pub fn with_diagnostic(music: Music, diagnostic: TransformDiagnostic) -> Self {
71        Self {
72            music,
73            diagnostics: vec![diagnostic],
74        }
75    }
76
77    /// Returns whether the report carries any diagnostics.
78    pub fn has_diagnostics(&self) -> bool {
79        !self.diagnostics.is_empty()
80    }
81}
82
83/// Named pitch map that applies a fixed chromatic offset to each pitch.
84#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct CallablePitchMap {
86    /// Display name of the map.
87    pub name: String,
88    /// Chromatic offset in semitones applied to each pitch.
89    pub semitone_delta: i32,
90}
91
92impl CallablePitchMap {
93    /// Builds a named pitch map with the given semitone offset.
94    pub fn new(name: impl Into<String>, semitone_delta: i32) -> Self {
95        Self {
96            name: name.into(),
97            semitone_delta,
98        }
99    }
100
101    /// Applies the map's offset to a pitch.
102    pub fn map_pitch(&self, pitch: Pitch) -> Pitch {
103        pitch.transpose(self.semitone_delta)
104    }
105}