sim_lib_pitch_set/
model.rs1use thiserror::Error;
2
3use sim_lib_pitch_core::{Pitch, PitchClass};
4
5#[derive(Debug, Error, Clone, PartialEq, Eq)]
7pub enum PitchSetError {
8 #[error("invalid MIDI key {0}")]
10 InvalidMidiKey(u8),
11 #[error("invalid third stack encoding")]
13 InvalidThirdStackEncoding,
14 #[error("invalid third stack signature")]
16 InvalidThirdStack,
17}
18
19#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
34pub struct PitchClassMask(pub u16);
35
36impl PitchClassMask {
37 pub fn from_pitch_classes(pitch_classes: &[PitchClass]) -> Self {
39 let mut bits = 0u16;
40 for pitch_class in pitch_classes {
41 bits |= 1u16 << pitch_class.0;
42 }
43 Self(bits)
44 }
45
46 pub fn pitch_classes(self) -> Vec<PitchClass> {
48 (0..12)
49 .filter(|bit| self.0 & (1u16 << bit) != 0)
50 .map(|bit| PitchClass(bit as u8))
51 .collect()
52 }
53
54 pub fn rotate(self, semitones: i32) -> Self {
56 let shift = semitones.rem_euclid(12) as u32;
57 let bits = self.0 & 0x0fff;
58 Self(((bits << shift) | (bits >> (12 - shift))) & 0x0fff)
59 }
60
61 pub fn invert(self, axis: PitchClass) -> Self {
63 let mut out = 0u16;
64 for pitch_class in self.pitch_classes() {
65 out |= 1u16 << pitch_class.invert(axis).0;
66 }
67 Self(out)
68 }
69
70 pub fn normalize(self) -> Self {
73 (0..12)
74 .map(|shift| self.rotate(-shift))
75 .min_by_key(|mask| mask.0)
76 .unwrap_or(self)
77 }
78
79 pub fn count_bits(self) -> u32 {
81 self.0.count_ones()
82 }
83
84 pub fn interval_vector(self) -> IntervalVector {
87 let pitch_classes = self.pitch_classes();
88 let mut bins = [0u16; 6];
89 for (index, a) in pitch_classes.iter().enumerate() {
90 for b in pitch_classes.iter().skip(index + 1) {
91 let class = a.interval_class(*b);
92 if class > 0 {
93 bins[(class - 1) as usize] += 1;
94 }
95 }
96 }
97 IntervalVector(bins)
98 }
99}
100
101#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
106pub struct PitchRangeMask {
107 pub bits: u128,
109}
110
111impl PitchRangeMask {
112 pub fn set(&mut self, midi_key: u8) {
114 self.bits |= 1u128 << midi_key;
115 }
116
117 pub fn clear(&mut self, midi_key: u8) {
119 self.bits &= !(1u128 << midi_key);
120 }
121
122 pub fn contains(self, midi_key: u8) -> bool {
124 self.bits & (1u128 << midi_key) != 0
125 }
126
127 pub fn union(self, other: Self) -> Self {
129 Self {
130 bits: self.bits | other.bits,
131 }
132 }
133
134 pub fn intersection(self, other: Self) -> Self {
136 Self {
137 bits: self.bits & other.bits,
138 }
139 }
140
141 pub fn difference(self, other: Self) -> Self {
143 Self {
144 bits: self.bits & !other.bits,
145 }
146 }
147
148 pub fn to_pitches(self) -> Vec<Pitch> {
150 (0..128u8)
151 .filter(|key| self.contains(*key))
152 .map(Pitch::from_midi)
153 .collect()
154 }
155}
156
157#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
160pub struct IntervalVector(pub [u16; 6]);
161
162#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
167pub struct BitChord {
168 pub mask: PitchClassMask,
170 pub root: Option<PitchClass>,
172}
173
174impl BitChord {
175 pub fn canonical(self) -> Self {
178 if self.root.is_some() {
179 self
180 } else {
181 Self {
182 mask: self.mask.normalize(),
183 root: None,
184 }
185 }
186 }
187}
188
189#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
192pub enum ThirdStep {
193 Minor,
195 Major,
197}
198
199#[derive(Clone, Debug, PartialEq, Eq, Hash)]
205pub struct ThirdStackSignature {
206 pub root: PitchClass,
208 pub steps: Vec<ThirdStep>,
210 pub guard: bool,
212}
213
214impl ThirdStackSignature {
215 pub fn validate(&self) -> Result<(), PitchSetError> {
218 let mut minor_run = 0usize;
219 let mut major_run = 0usize;
220 for step in &self.steps {
221 match step {
222 ThirdStep::Minor => {
223 minor_run += 1;
224 major_run = 0;
225 }
226 ThirdStep::Major => {
227 major_run += 1;
228 minor_run = 0;
229 }
230 }
231 if minor_run >= 4 || major_run >= 3 {
232 return Err(PitchSetError::InvalidThirdStack);
233 }
234 }
235 Ok(())
236 }
237
238 pub fn encode(&self) -> Result<u32, PitchSetError> {
240 self.validate()?;
241 let mut encoded = self.root.0 as u32;
242 for (index, step) in self.steps.iter().enumerate() {
243 let bit = matches!(step, ThirdStep::Major) as u32;
244 encoded |= bit << (4 + index);
245 }
246 if self.guard {
247 encoded |= 1u32 << (4 + self.steps.len());
248 }
249 Ok(encoded)
250 }
251
252 pub fn decode(encoded: u32) -> Result<Self, PitchSetError> {
255 let root = PitchClass((encoded & 0x0f) as u8);
256 let mut steps = Vec::new();
257 let mut index = 4u32;
258 let mut guard = false;
259 while index < 31 {
260 let bit = (encoded >> index) & 1;
261 if ((encoded >> (index + 1)) & 1) == 0 && bit == 1 && index > 4 {
262 guard = true;
263 break;
264 }
265 steps.push(if bit == 0 {
266 ThirdStep::Minor
267 } else {
268 ThirdStep::Major
269 });
270 index += 1;
271 if steps.len() >= 8 {
272 break;
273 }
274 }
275 let signature = Self { root, steps, guard };
276 signature.validate()?;
277 Ok(signature)
278 }
279
280 pub fn family_tag(&self) -> char {
283 let majors = self
284 .steps
285 .iter()
286 .filter(|step| matches!(step, ThirdStep::Major))
287 .count();
288 match majors {
289 0..=2 => 'w',
290 3 => 'x',
291 4 => 'y',
292 _ => 'z',
293 }
294 }
295
296 pub fn to_mask(&self) -> PitchClassMask {
299 let mut pitch_classes = vec![self.root];
300 let mut current = self.root;
301 for step in &self.steps {
302 current = current.transpose(match step {
303 ThirdStep::Minor => 3,
304 ThirdStep::Major => 4,
305 });
306 pitch_classes.push(current);
307 }
308 PitchClassMask::from_pitch_classes(&pitch_classes)
309 }
310}