1mod dataset;
27pub mod jsymbolic;
28pub mod language;
31pub mod native;
32
33pub use dataset::{
34 Cell, DataSet, Failure, Library, OutputFormat, all_features_as_list, extractor_by_id,
35 extractors_by_id, index_of, vector_by_id,
36};
37
38use std::cell::OnceCell;
39
40use crate::{
41 analysis::{self, KeyEstimate, KeyProfile, pitch_analysis},
42 chord::Chord,
43 defaults::{FloatType, IntegerType},
44 error::{Error, Result},
45 meter::TimeSignature,
46 pitch::Pitch,
47 stream::{ConsecutiveOptions, Stream, StreamElement, StreamEvent, StreamKind},
48 tempo::MetronomeMark,
49};
50
51#[derive(Clone, Copy, Debug, PartialEq)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub enum Value {
58 Integer(i64),
60 Float(FloatType),
62}
63
64impl Value {
65 pub fn as_float(self) -> FloatType {
67 match self {
68 Self::Integer(value) => value as FloatType,
69 Self::Float(value) => value,
70 }
71 }
72
73 pub fn is_integer(self) -> bool {
75 matches!(self, Self::Integer(_))
76 }
77}
78
79impl Default for Value {
80 fn default() -> Self {
81 Self::Integer(0)
82 }
83}
84
85impl std::fmt::Display for Value {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 Self::Integer(value) => write!(f, "{value}"),
91 Self::Float(value) => f.write_str(&crate::statistics::python_repr(*value)),
92 }
93 }
94}
95
96macro_rules! whole_values {
97 ($($kind:ty),*) => {$(
98 impl From<$kind> for Value {
99 fn from(value: $kind) -> Self {
100 Self::Integer(value as i64)
101 }
102 }
103 )*};
104}
105
106whole_values!(i32, i64, u8, u32, usize);
107
108impl From<FloatType> for Value {
109 fn from(value: FloatType) -> Self {
110 Self::Float(value)
111 }
112}
113
114#[derive(Clone, Debug, PartialEq)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117pub struct Feature {
118 id: String,
119 name: String,
120 discrete: bool,
121 values: Vec<Value>,
122}
123
124impl Feature {
125 pub fn id(&self) -> &str {
127 &self.id
128 }
129
130 pub fn name(&self) -> &str {
132 &self.name
133 }
134
135 pub fn discrete(&self) -> bool {
137 self.discrete
138 }
139
140 pub fn vector(&self) -> Vec<FloatType> {
142 self.values.iter().map(|value| value.as_float()).collect()
143 }
144
145 pub fn values(&self) -> &[Value] {
147 &self.values
148 }
149
150 pub fn attribute_labels(&self) -> Vec<String> {
153 attribute_labels(&self.name, self.values.len())
154 }
155}
156
157fn attribute_labels(name: &str, dimensions: usize) -> Vec<String> {
160 let name = name.replace(' ', "_");
161 if dimensions == 1 {
162 vec![name]
163 } else {
164 (0..dimensions)
165 .map(|index| format!("{name}_{index}"))
166 .collect()
167 }
168}
169
170#[derive(Clone, Copy)]
173pub struct Extractor {
174 id: &'static str,
175 name: &'static str,
176 description: &'static str,
177 dimensions: usize,
178 discrete: bool,
179 normalize: bool,
180 process: fn(&DataInstance, &mut [Value]) -> Result<()>,
181}
182
183impl std::fmt::Debug for Extractor {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 f.debug_struct("Extractor")
186 .field("id", &self.id)
187 .field("name", &self.name)
188 .field("dimensions", &self.dimensions)
189 .finish()
190 }
191}
192
193impl Extractor {
194 pub fn id(&self) -> &'static str {
196 self.id
197 }
198
199 pub fn name(&self) -> &'static str {
201 self.name
202 }
203
204 pub fn description(&self) -> &'static str {
206 self.description
207 }
208
209 pub fn dimensions(&self) -> usize {
211 self.dimensions
212 }
213
214 pub fn discrete(&self) -> bool {
216 self.discrete
217 }
218
219 pub fn normalize(&self) -> bool {
221 self.normalize
222 }
223
224 pub fn attribute_labels(&self) -> Vec<String> {
227 attribute_labels(self.name, self.dimensions)
228 }
229
230 pub fn blank(&self) -> Feature {
234 Feature {
235 id: self.id.to_string(),
236 name: self.name.to_string(),
237 discrete: self.discrete,
238 values: vec![Value::Integer(0); self.dimensions],
239 }
240 }
241
242 pub fn extract(&self, data: &DataInstance) -> Result<Feature> {
249 let mut feature = self.blank();
250 (self.process)(data, &mut feature.values)?;
251 if self.normalize {
252 let sum = python_sum_of(&feature.values);
253 if sum == 0.0 {
254 return Err(Error::Feature("cannot normalize zero vector".to_string()));
255 }
256 let scalar = 1.0 / sum;
258 for value in &mut feature.values {
259 *value = Value::Float(value.as_float() * scalar);
260 }
261 }
262 Ok(feature)
263 }
264}
265
266fn python_sum_of(values: &[Value]) -> FloatType {
270 let whole = values.iter().take_while(|value| value.is_integer()).count();
271 let exact: i64 = values[..whole]
272 .iter()
273 .map(|value| match value {
274 Value::Integer(value) => *value,
275 Value::Float(_) => 0,
276 })
277 .sum();
278 let mut rest = vec![exact as FloatType];
279 rest.extend(values[whole..].iter().map(|value| value.as_float()));
280 crate::statistics::python_sum(&rest)
281}
282
283#[derive(Debug)]
286pub struct DataInstance {
287 prepared: Stream,
288 parts: Vec<DataInstance>,
291 parts_count: usize,
292 pitches: OnceCell<Vec<Pitch>>,
293 midi_pitch_histogram: OnceCell<Vec<(IntegerType, usize)>>,
294 pitch_class_histogram: OnceCell<[usize; 12]>,
295 midi_interval_histogram: OnceCell<Vec<usize>>,
296 contour: OnceCell<Vec<IntegerType>>,
297 seconds: OnceCell<std::result::Result<Vec<Seconds>, Error>>,
298 chordified: OnceCell<std::result::Result<Chordified, Error>>,
299 quarter_length_histogram: OnceCell<Vec<(FloatType, usize)>>,
300 analyzed_key: OnceCell<Option<Vec<KeyEstimate>>>,
301}
302
303#[derive(Debug)]
305struct Chordified {
306 chords: Vec<Chord>,
307 parts_sounding: Vec<usize>,
308}
309
310#[derive(Clone, Copy, Debug, PartialEq)]
313#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
314pub struct Seconds {
315 pub offset: FloatType,
317 pub duration: FloatType,
319 pub end: FloatType,
321}
322
323impl DataInstance {
324 pub fn new(stream: &Stream) -> Result<Self> {
331 let mut instance = Self::forms(stream)?;
332 if stream.kind() == StreamKind::Score {
333 let parts = stream.parts();
334 instance.parts_count = parts.len();
335 for part in parts {
336 instance.parts.push(Self::forms(part)?);
337 }
338 }
339 for (_, element) in stream.recurse() {
340 if let StreamElement::Stream(voice) = element
341 && voice.kind() == StreamKind::Voice
342 {
343 instance.parts.push(Self::forms(voice)?);
344 }
345 }
346 Ok(instance)
347 }
348
349 fn forms(stream: &Stream) -> Result<Self> {
351 let mut prepared = stream.clone();
352 prepared.strip_ties(true)?;
353 Ok(Self {
354 prepared,
355 parts: Vec::new(),
356 parts_count: 0,
357 pitches: OnceCell::new(),
358 midi_pitch_histogram: OnceCell::new(),
359 pitch_class_histogram: OnceCell::new(),
360 midi_interval_histogram: OnceCell::new(),
361 contour: OnceCell::new(),
362 seconds: OnceCell::new(),
363 chordified: OnceCell::new(),
364 quarter_length_histogram: OnceCell::new(),
365 analyzed_key: OnceCell::new(),
366 })
367 }
368
369 pub fn prepared(&self) -> &Stream {
371 &self.prepared
372 }
373
374 pub fn parts(&self) -> &[DataInstance] {
377 &self.parts
378 }
379
380 pub fn parts_count(&self) -> usize {
383 self.parts_count
384 }
385
386 pub fn pitches(&self) -> &[Pitch] {
389 self.pitches
390 .get_or_init(|| pitch_analysis::music21_pitches(&self.prepared).unwrap_or_default())
391 }
392
393 pub fn midi_pitch_histogram(&self) -> &[(IntegerType, usize)] {
396 self.midi_pitch_histogram.get_or_init(|| {
397 let mut counts: Vec<(IntegerType, usize)> = Vec::new();
398 for pitch in self.pitches() {
399 let midi = pitch.midi();
400 match counts.iter_mut().find(|(known, _)| *known == midi) {
401 Some((_, count)) => *count += 1,
402 None => counts.push((midi, 1)),
403 }
404 }
405 counts
406 })
407 }
408
409 pub fn pitch_class_histogram(&self) -> &[usize; 12] {
411 self.pitch_class_histogram.get_or_init(|| {
412 let mut counts = [0; 12];
413 for pitch in self.pitches() {
414 counts[(pitch.ps().round_ties_even() as i64).rem_euclid(12) as usize] += 1;
417 }
418 counts
419 })
420 }
421
422 pub fn midi_interval_histogram(&self) -> &[usize] {
426 self.midi_interval_histogram.get_or_init(|| {
427 let mut counts = vec![0; 128];
428 let options = ConsecutiveOptions {
429 skip_rests: true,
430 skip_chords: true,
431 skip_gaps: true,
432 no_none: true,
433 ..ConsecutiveOptions::default()
434 };
435 for part in self.melodic_parts(self.prepared.kind() == StreamKind::Score) {
436 let midis = consecutive_midis(part, &options, false);
437 for pair in midis.windows(2) {
438 let size = (pair[0] - pair[1]).unsigned_abs() as usize;
439 if let Some(count) = counts.get_mut(size) {
440 *count += 1;
441 }
442 }
443 }
444 counts
445 })
446 }
447
448 pub fn contour_list(&self) -> &[IntegerType] {
451 self.contour.get_or_init(|| {
452 let options = ConsecutiveOptions {
453 skip_rests: true,
454 skip_gaps: true,
455 no_none: true,
456 ..ConsecutiveOptions::default()
457 };
458 let mut contour = Vec::new();
459 for part in self.melodic_parts(self.prepared.has_part_like_streams()) {
460 let midis = consecutive_midis(part, &options, true);
461 contour.extend(midis.windows(2).map(|pair| pair[1] - pair[0]));
462 }
463 contour
464 })
465 }
466
467 pub fn metronome_mark_boundaries(&self) -> Result<Vec<(FloatType, FloatType, MetronomeMark)>> {
475 self.prepared.metronome_mark_boundaries()
476 }
477
478 pub fn seconds_map(&self) -> Result<&[Seconds]> {
489 self.seconds
490 .get_or_init(|| self.work_out_seconds())
491 .as_deref()
492 .map_err(Clone::clone)
493 }
494
495 fn work_out_seconds(&self) -> Result<Vec<Seconds>> {
496 let boundaries = self.metronome_mark_boundaries()?;
497 let flat = self.prepared.flatten();
498 let lowest = flat.events().first().map_or(0.0, StreamEvent::offset);
499 let mut seconds = Vec::new();
500 for event in flat.events() {
501 let element = event.element();
502 if !matches!(
503 element,
504 StreamElement::Note(_)
505 | StreamElement::Chord(_)
506 | StreamElement::Unpitched(_)
507 | StreamElement::PercussionChord(_)
508 | StreamElement::ChordSymbol(_)
509 ) {
510 continue;
511 }
512 let offset = crate::statistics::python_round(event.offset(), 8);
514 let start = accumulated_seconds(&boundaries, lowest, offset)?;
515 let duration =
516 accumulated_seconds(&boundaries, offset, offset + element.quarter_length())?;
517 seconds.push(Seconds {
518 offset: start,
519 duration,
520 end: start + duration,
521 });
522 }
523 Ok(seconds)
524 }
525
526 pub fn parts_sounding(&self) -> Result<&[usize]> {
536 Ok(&self.chordified()?.parts_sounding)
537 }
538
539 pub fn chordified_chords(&self) -> Result<&[Chord]> {
548 Ok(&self.chordified()?.chords)
549 }
550
551 fn chordified(&self) -> Result<&Chordified> {
552 self.chordified
553 .get_or_init(|| {
554 if self.prepared.kind() == StreamKind::Score {
555 let (chordified, parts_sounding) = self.prepared.chordify_parts_sounding()?;
556 Ok(Chordified {
557 chords: chords_of(&chordified)?,
558 parts_sounding,
559 })
560 } else {
561 let chords = chords_of(&self.prepared)?;
562 Ok(Chordified {
563 parts_sounding: vec![0; chords.len()],
564 chords,
565 })
566 }
567 })
568 .as_ref()
569 .map_err(Clone::clone)
570 }
571
572 pub fn quarter_length_histogram(&self) -> &[(FloatType, usize)] {
576 self.quarter_length_histogram.get_or_init(|| {
577 let mut counts: Vec<(FloatType, usize)> = Vec::new();
578 for event in self.prepared.flatten().events() {
579 let element = event.element();
580 if !matches!(
581 element,
582 StreamElement::Note(_)
583 | StreamElement::Chord(_)
584 | StreamElement::Unpitched(_)
585 | StreamElement::PercussionChord(_)
586 | StreamElement::ChordSymbol(_)
587 ) {
588 continue;
589 }
590 let length = element.quarter_length();
591 match counts.iter_mut().find(|(known, _)| *known == length) {
592 Some((_, count)) => *count += 1,
593 None => counts.push((length, 1)),
594 }
595 }
596 counts
597 })
598 }
599
600 pub fn analyzed_key(&self) -> Option<&[KeyEstimate]> {
605 self.analyzed_key
606 .get_or_init(|| {
607 analysis::estimate_key_of_stream(KeyProfile::AardenEssen, &self.prepared)
608 })
609 .as_deref()
610 }
611
612 fn time_signatures(&self) -> Vec<TimeSignature> {
614 self.prepared
615 .flatten()
616 .events()
617 .iter()
618 .filter_map(|event| match event.element() {
619 StreamElement::TimeSignature(meter) => Some(meter.clone()),
620 _ => None,
621 })
622 .collect()
623 }
624
625 fn key_modes(&self) -> Vec<String> {
627 self.prepared
628 .flatten()
629 .events()
630 .iter()
631 .filter_map(|event| match event.element() {
632 StreamElement::Key(key) => Some(key.mode().to_string()),
633 _ => None,
634 })
635 .collect()
636 }
637
638 fn melodic_parts(&self, by_part: bool) -> Vec<&Stream> {
639 if by_part {
640 self.prepared.parts()
641 } else {
642 vec![&self.prepared]
643 }
644 }
645}
646
647fn chords_of(stream: &Stream) -> Result<Vec<Chord>> {
649 stream
650 .flatten()
651 .events()
652 .iter()
653 .filter_map(|event| match event.element() {
654 StreamElement::Chord(chord) => Some(Ok(chord.clone())),
655 StreamElement::ChordSymbol(symbol) if symbol.is_no_chord() => {
656 Some(Chord::new(&[] as &[Pitch]))
657 }
658 StreamElement::ChordSymbol(symbol) => Some(symbol.to_chord()),
659 _ => None,
660 })
661 .collect()
662}
663
664fn consecutive_midis(
667 part: &Stream,
668 options: &ConsecutiveOptions,
669 chords: bool,
670) -> Vec<IntegerType> {
671 let leaves = part.leaves();
672 let found = part.find_consecutive_notes(options).unwrap_or_default();
673 found
674 .into_iter()
675 .flatten()
676 .filter_map(|position| match leaves[position].1 {
677 StreamElement::Note(note) => Some(note.pitch().midi()),
678 StreamElement::Chord(chord) if chords => chord
679 .sort_diatonic_ascending()
680 .pitches()
681 .last()
682 .map(Pitch::midi),
683 StreamElement::ChordSymbol(symbol) if chords => {
684 let mut pitches = symbol.pitches().ok()?;
685 pitches.sort_by(|left, right| {
686 (left.diatonic_note_number(), left.ps())
687 .partial_cmp(&(right.diatonic_note_number(), right.ps()))
688 .unwrap_or(std::cmp::Ordering::Equal)
689 });
690 pitches.last().map(Pitch::midi)
691 }
692 _ => None,
693 })
694 .collect()
695}
696
697fn accumulated_seconds(
700 boundaries: &[(FloatType, FloatType, MetronomeMark)],
701 start: FloatType,
702 end: FloatType,
703) -> Result<FloatType> {
704 let mut total = 0.0;
705 let mut active_start = start;
706 for (from, to, mark) in boundaries {
707 if !(*from <= active_start && active_start < *to) {
708 continue;
709 }
710 let active_end = if end < *to { end } else { *to };
711 total += mark.seconds_per_quarter()? * (active_end - active_start);
712 if active_end == end {
713 break;
714 }
715 active_start = active_end;
716 }
717 Ok(total)
718}