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