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}