1use sim_lib_pitch_chord::ChordTemplate;
4use sim_lib_pitch_core::PitchClass;
5use sim_lib_pitch_namer_roman::label_roman;
6use sim_lib_pitch_ratio::{PitchRatio, RatioPolicy, analyze_ratio_chord};
7use sim_lib_pitch_scale::{Key, Scale};
8use sim_lib_pitch_serial::RowForm;
9use sim_lib_pitch_set::PitchClassMask;
10
11use crate::{ReferentialEmphasis, techniques::referential::build_referential_report};
12
13#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub enum ReferentialClaim {
16 EmbeddedSubset,
18 NoReferentialSubset,
20}
21
22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
24pub enum ReferentialEvidenceKind {
25 Chord,
27 Scale,
29 KeyRegion,
31 PitchRatio,
33 Mask,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub enum ReferentialEvidence {
40 Chord {
42 label: String,
44 chord: ChordTemplate,
46 },
47 Scale {
49 label: String,
51 scale: Scale,
53 },
54 KeyRegion {
56 label: String,
58 key: Key,
60 preferred_root: Option<PitchClass>,
62 },
63 PitchRatio {
65 label: String,
67 ratios: Vec<PitchRatio>,
69 policy: RatioPolicy,
71 },
72 Mask {
74 label: String,
76 mask: PitchClassMask,
78 },
79}
80
81impl ReferentialEvidence {
82 pub fn label(&self) -> &str {
84 match self {
85 Self::Chord { label, .. }
86 | Self::Scale { label, .. }
87 | Self::KeyRegion { label, .. }
88 | Self::PitchRatio { label, .. }
89 | Self::Mask { label, .. } => label,
90 }
91 }
92
93 pub const fn kind(&self) -> ReferentialEvidenceKind {
95 match self {
96 Self::Chord { .. } => ReferentialEvidenceKind::Chord,
97 Self::Scale { .. } => ReferentialEvidenceKind::Scale,
98 Self::KeyRegion { .. } => ReferentialEvidenceKind::KeyRegion,
99 Self::PitchRatio { .. } => ReferentialEvidenceKind::PitchRatio,
100 Self::Mask { .. } => ReferentialEvidenceKind::Mask,
101 }
102 }
103}
104
105#[derive(Clone, Debug, PartialEq)]
107pub struct ReferentialRatioSummary {
108 pub policy: RatioPolicy,
110 pub admitted_tones: usize,
112 pub rejected_tones: usize,
114 pub cost: f64,
116}
117
118#[derive(Clone, Debug, PartialEq)]
120pub struct ReferentialContextReport {
121 pub evidence_kind: ReferentialEvidenceKind,
123 pub evidence_label: String,
125 pub harmonic_claim: Option<String>,
127 pub subset_classes: Vec<PitchClass>,
129 pub subset_mask: PitchClassMask,
131 pub scale_degrees: Vec<usize>,
133 pub ratio_summary: Option<ReferentialRatioSummary>,
135}
136
137#[derive(Clone, Debug, PartialEq)]
139pub struct ReferentialRequest {
140 pub ordinals: Vec<usize>,
142 pub evidence: ReferentialEvidence,
144 pub emphasis: ReferentialEmphasis,
146}
147
148#[derive(Clone, Debug, PartialEq)]
150pub struct ReferentialReport {
151 pub claim: ReferentialClaim,
153 pub ordinals: Vec<usize>,
155 pub context: ReferentialContextReport,
157 pub emphasis: ReferentialEmphasis,
159 pub claims_whole_passage_tonality: bool,
161}
162
163pub fn analyze_referential_subset(
165 row_form: &RowForm,
166 request: ReferentialRequest,
167) -> Result<ReferentialReport, String> {
168 build_referential_report(row_form, request)
169}
170
171pub(crate) fn roman_claim(
172 subset_mask: PitchClassMask,
173 key: Key,
174 preferred_root: Option<PitchClass>,
175) -> Option<String> {
176 label_roman(subset_mask, Some(key), preferred_root).ok()
177}
178
179pub(crate) fn scale_degrees(scale: Scale, classes: &[PitchClass]) -> Vec<usize> {
180 classes
181 .iter()
182 .filter_map(|class| scale.degree_of(*class))
183 .collect()
184}
185
186pub(crate) fn ratio_summary(
187 ratios: &[PitchRatio],
188 policy: RatioPolicy,
189) -> Result<ReferentialRatioSummary, String> {
190 let report = analyze_ratio_chord(ratios, policy).map_err(|error| error.to_string())?;
191 Ok(ReferentialRatioSummary {
192 policy,
193 admitted_tones: report.covered.admitted_tones,
194 rejected_tones: report.covered.rejected_tones,
195 cost: report.cost,
196 })
197}