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