1use crate::core::rstmt::prelude::{HarmonicFunction, LPR, PitchMod, Triad, Triads};
6use crate::ctx::ActorContext;
7use crate::drivers::RawDriver;
8use crate::mem::TopoLedger;
9use crate::{Actor, VNode};
10use rshyper::EdgeId;
11use scsys::Id;
12
13use num_traits::{Float, FromPrimitive, NumAssign};
14
15pub(crate) type Observations = alloc::collections::VecDeque<(LPR, Triads, Triads)>;
18pub(crate) type TonalRegions = std::collections::HashMap<[usize; 3], String>;
20
21#[derive(Clone, Debug)]
25pub struct Observer<T = f32> {
26 pub(crate) id: Id,
27 pub(crate) threshold: T,
29 pub(crate) observations: Observations,
31 pub(crate) tonal_regions: TonalRegions,
33 pub(crate) initialized: bool,
35}
36
37impl<T> Observer<T> {
38 pub fn new() -> Self
40 where
41 T: FromPrimitive,
42 {
43 let id: Id;
44 #[cfg(feature = "rand")]
45 {
46 id = Id::<u128>::random().map(|id| id as usize);
47 }
48 #[cfg(not(feature = "rand"))]
49 {
50 id = Id::atomic();
51 }
52 Observer {
53 id,
54 threshold: T::from_f32(0.95).unwrap(),
55 observations: Observations::new(),
56 tonal_regions: TonalRegions::new(),
57 initialized: false,
58 }
59 }
60 pub const fn id(&self) -> Id {
62 self.id
63 }
64 pub const fn is_initialized(&self) -> bool {
66 self.initialized
67 }
68 pub const fn is_uninitialized(&self) -> bool {
70 !self.initialized
71 }
72 pub const fn threshold(&self) -> &T {
74 &self.threshold
75 }
76 pub const fn threshold_mut(&mut self) -> &mut T {
78 &mut self.threshold
79 }
80 pub const fn observations(&self) -> &Observations {
82 &self.observations
83 }
84 pub const fn observations_mut(&mut self) -> &mut Observations {
86 &mut self.observations
87 }
88 pub const fn tonal_regions(&self) -> &TonalRegions {
90 &self.tonal_regions
91 }
92 pub const fn tonal_regions_mut(&mut self) -> &mut TonalRegions {
94 &mut self.tonal_regions
95 }
96 pub fn set_id(&mut self, id: Id) -> &mut Self {
98 self.id = id;
99 self
100 }
101 pub fn set_threshold(&mut self, threshold: T) -> &mut Self
103 where
104 T: Float,
105 {
106 self.threshold = threshold.max(T::zero()).min(T::one());
107 self
108 }
109 pub fn set_observations(&mut self, observations: Observations) -> &mut Self {
111 self.observations = observations;
112 self
113 }
114 pub fn set_tonal_regions(&mut self, tonal_regions: TonalRegions) -> &mut Self {
116 self.tonal_regions = tonal_regions;
117 self
118 }
119 pub fn initialized(self) -> Self {
121 Self {
122 initialized: !self.initialized,
123 ..self
124 }
125 }
126 pub fn with_id(self, id: Id) -> Self {
128 Self { id, ..self }
129 }
130 pub fn with_observations(self, observations: Observations) -> Self {
132 Self {
133 observations,
134 ..self
135 }
136 }
137 pub fn with_threshold(self, threshold: T) -> Self
139 where
140 T: Float,
141 {
142 Self {
143 threshold: threshold.max(T::zero()).min(T::one()),
144 ..self
145 }
146 }
147 pub fn with_tonal_regions(self, tonal_regions: TonalRegions) -> Self {
149 Self {
150 tonal_regions,
151 ..self
152 }
153 }
154 pub fn initialize(self) -> Self {
156 Self {
157 initialized: true,
158 ..self
159 }
160 }
161 pub fn observation_count(&self) -> usize {
163 self.observations.len()
164 }
165
166 pub fn analyze_patterns(&self, memory: &TopoLedger<T>) -> Vec<(Vec<LPR>, T)>
168 where
169 T: Float + FromPrimitive,
170 {
171 let mut patterns = Vec::new();
172
173 let lpr_sequence: Vec<_> = self.observations.iter().map(|(lpr, _, _)| *lpr).collect();
175
176 if lpr_sequence.len() < 3 {
177 return patterns;
178 }
179
180 for window_size in 2..=lpr_sequence.len() / 2 {
182 for start in 0..=lpr_sequence.len() - window_size {
183 let pattern = lpr_sequence[start..start + window_size].to_vec();
184 let mut count = 0;
185
186 for mem_pattern in memory.patterns() {
188 if mem_pattern.sequence_len() >= pattern.len() {
189 for i in 0..=mem_pattern.sequence_len() - pattern.len() {
190 let mut matches = true;
191 for j in 0..pattern.len() {
192 let mem_lpr = match mem_pattern[i + j] {
194 0 => LPR::Leading,
195 1 => LPR::Parallel,
196 2 => LPR::Relative,
197 _ => continue,
198 };
199
200 if mem_lpr != pattern[j] {
201 matches = false;
202 break;
203 }
204 }
205
206 if matches {
207 count += 1;
208 }
209 }
210 }
211 }
212
213 if count > 1 {
215 let importance = T::from_usize(count).unwrap()
216 / T::from_usize(memory.patterns().len()).unwrap();
217 if &importance >= self.threshold() {
218 patterns.push((pattern, importance));
219 }
220 }
221 }
222 }
223
224 patterns.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal));
226 patterns
227 }
228 fn analyze_pattern(&self, pattern: &[usize]) -> Vec<String> {
230 if pattern.is_empty() {
231 return Vec::new();
232 }
233
234 let mut meanings = Vec::new();
235
236 if pattern == &[2, 1] {
238 meanings.push("Cadential".to_string());
240 } else if pattern == &[0, 1] {
241 meanings.push("Deceptive".to_string());
243 } else if pattern == &[0, 0] {
244 meanings.push("Sequential".to_string());
246 } else if pattern == &[1, 1, 1] {
247 meanings.push("Modal Mixture".to_string());
249 } else if pattern == &[2, 2] {
250 meanings.push("Common-tone Modulation".to_string());
252 }
253
254 let mut has_symmetry = false;
256 if pattern.len() >= 4 {
257 let mut is_palindrome = true;
259 for i in 0..pattern.len() / 2 {
260 if pattern[i] != pattern[pattern.len() - 1 - i] {
261 is_palindrome = false;
262 break;
263 }
264 }
265 has_symmetry = is_palindrome;
266 }
267
268 if has_symmetry {
269 meanings.push("Symmetrical".to_string());
270 }
271
272 if pattern.len() >= 3 {
274 'cycle_check: for cycle_len in 1..=pattern.len() / 2 {
275 if pattern.len() % cycle_len != 0 {
276 continue;
277 }
278
279 let mut is_cycle = true;
280 for i in 0..pattern.len() - cycle_len {
281 if pattern[i] != pattern[i + cycle_len] {
282 is_cycle = false;
283 break;
284 }
285 }
286
287 if is_cycle {
288 meanings.push(format!("Cyclic-{}", cycle_len));
289 break 'cycle_check;
290 }
291 }
292 }
293
294 let mut counts = [0, 0, 0]; for &p in pattern {
297 if p < 3 {
298 counts[p] += 1;
299 }
300 }
301
302 let total = pattern.len();
304 if counts[0] > total / 2 {
305 meanings.push("Leading-dominated".to_string());
306 } else if counts[1] > total / 2 {
307 meanings.push("Parallel-dominated".to_string());
308 } else if counts[2] > total / 2 {
309 meanings.push("Relative-dominated".to_string());
310 } else if counts[0] == counts[1] && counts[1] == counts[2] && counts[0] > 0 {
311 meanings.push("Balanced".to_string());
312 }
313
314 meanings
315 }
316
317 fn identify_tonal_region(&self, triad: &Triad) -> Option<String> {
319 self.tonal_regions().get(&triad.notes()).cloned()
321 }
322
323 pub fn analyze_critical_points<D>(&self, vnode: &VNode<D>) -> Vec<(String, T)>
325 where
326 D: RawDriver<Triad>,
327 T: Float + FromPrimitive,
328 {
329 let mut analysis = Vec::new();
330
331 let triad = vnode.headspace();
333 let critical_points = vnode.critical_points();
334
335 for (name, point) in critical_points {
336 let root = triad.root();
339 let distance = (((*point as isize) - (root as isize)).abs().pmod()) as usize;
340
341 let normalized_distance = T::from_usize(distance).unwrap() / T::from_usize(6).unwrap(); let relationship = match distance {
346 0 => "At",
347 1 => "Adjacent to",
348 2 => "Near",
349 _ => "Distant from",
350 };
351
352 analysis.push((
353 format!("{} {}", relationship, name),
354 T::one() - normalized_distance,
355 ));
356 }
357
358 analysis
359 }
360}
361
362impl<D, T> Actor<D, T> for Observer<T>
363where
364 D: RawDriver<Triad>,
365 T: core::iter::Sum + Float + FromPrimitive + NumAssign,
366{
367 fn kind(&self) -> &'static str {
368 super::OperatorKind::Observer.as_ref()
369 }
370
371 fn initialize(&mut self) -> crate::Result<()> {
372 if !self.initialized {
373 self.observations.clear();
374 self.initialized = true;
375 }
376 Ok(())
377 }
378
379 fn process_transform(
380 &mut self,
381 transform: LPR,
382 plant: &mut D,
383 _memory: &mut TopoLedger<T>,
384 ) -> crate::Result<bool> {
385 let from_class = plant.headspace().class();
387 let to_triad = plant.headspace().transform(transform);
389 let to_class = to_triad.class();
390
391 self.observations
393 .push_back((transform, from_class, to_class));
394
395 if self.observation_count() > 50 {
397 self.observations_mut().pop_front();
398 }
399
400 Ok(true) }
403
404 fn process_message(
405 &mut self,
406 _source: EdgeId,
407 _message: &[u8],
408 _memory: &mut TopoLedger<T>,
409 ) -> crate::Result<()> {
410 Ok(())
412 }
413
414 fn on_activate(&mut self, _plant: &D, _memory: &mut TopoLedger<T>) -> crate::Result<()> {
415 self.observations.clear();
416 Ok(())
417 }
418
419 fn on_deactivate(&mut self, _plant: &D, memory: &mut TopoLedger<T>) -> crate::Result<()> {
420 for i in 2..=4 {
422 if self.observation_count() >= i {
424 let recent: Vec<usize> = self
426 .observations
427 .iter()
428 .rev()
429 .take(i)
430 .map(|(lpr, _, _)| match lpr {
431 LPR::Leading => 0,
432 LPR::Parallel => 1,
433 LPR::Relative => 2,
434 })
435 .collect();
436
437 memory.record_pattern(&recent);
439 }
440 }
441
442 Ok(())
443 }
444
445 fn resource_requirements(&self) -> (usize, usize) {
446 (50, 10)
448 }
449
450 fn allows_pattern_sharing(&self) -> bool {
451 true }
453
454 fn contextualize(&self, plant: &D, memory: &TopoLedger<T>) -> crate::Result<ActorContext<T>> {
455 let triad = plant.headspace();
456
457 let stability = if memory.count_features() > 0 {
459 memory.get_stability_for(&triad.notes())
460 } else {
461 match triad.class() {
463 crate::nrt::Triads::Major => T::from_f32(0.8).unwrap(),
464 crate::nrt::Triads::Minor => T::from_f32(0.7).unwrap(),
465 _ => T::from_f32(0.5).unwrap(),
466 }
467 };
468
469 let harmonic_function = match triad.root().pmod() {
472 0 => Some(HarmonicFunction::Tonic), 7 => Some(HarmonicFunction::Dominant), 5 => Some(HarmonicFunction::Subdominant), 9 => Some(HarmonicFunction::Submediant), 2 => Some(HarmonicFunction::Supertonic), 11 => Some(HarmonicFunction::LeadingTone), 4 => Some(HarmonicFunction::Mediant), _ => None,
480 };
481
482 let mut semantic_tags = Vec::new();
484
485 if let Some(region) = self.identify_tonal_region(triad) {
487 semantic_tags.push(format!("Region:{}", region));
488 }
489
490 if let Some(recent_pattern) = memory.get_recent_pattern(5) {
492 let pattern_meanings = self.analyze_pattern(&recent_pattern);
494 semantic_tags.extend(pattern_meanings);
495 }
496
497 if stability > T::from_f32(0.8).unwrap() {
499 semantic_tags.push("Stable".to_string());
500 } else if stability < T::from_f32(0.4).unwrap() {
501 semantic_tags.push("Unstable".to_string());
502 }
503
504 Ok(ActorContext {
506 harmonic_function,
507 metric_position: None, semantic_tags,
509 stability,
510 })
511 }
512}
513
514impl<T> Default for Observer<T>
515where
516 T: Default + FromPrimitive,
517{
518 fn default() -> Self {
519 Observer::new()
520 }
521}
522
523impl<T> PartialEq<Observer<T>> for Observer<T> {
524 fn eq(&self, other: &Observer<T>) -> bool {
525 self.id() == other.id()
526 }
527}
528
529impl<T> PartialEq<Observer<T>> for &Observer<T> {
530 fn eq(&self, other: &Observer<T>) -> bool {
531 self.id() == other.id()
532 }
533}
534
535impl<T> PartialEq<Observer<T>> for &mut Observer<T> {
536 fn eq(&self, other: &Observer<T>) -> bool {
537 self.id() == other.id()
538 }
539}
540
541impl<'a, T> PartialEq<&'a Observer<T>> for Observer<T> {
542 fn eq(&self, other: &&'a Observer<T>) -> bool {
543 self.id() == other.id()
544 }
545}
546
547impl<'a, T> PartialEq<&'a mut Observer<T>> for Observer<T> {
548 fn eq(&self, other: &&'a mut Observer<T>) -> bool {
549 self.id() == other.id()
550 }
551}
552
553impl<T> Eq for Observer<T> {}
554
555impl<T> core::hash::Hash for Observer<T> {
556 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
557 self.id().hash(state);
558 self.observations().hash(state);
560 self.is_initialized().hash(state);
561 }
562}