1use super::{DataInstance, Extractor, Feature, Value, jsymbolic, native};
6use crate::{
7 error::{Error, Result},
8 metadata::Metadata,
9 stream::Stream,
10};
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub enum Library {
16 JSymbolic,
18 Native,
20}
21
22impl Library {
23 pub const ALL: [Library; 2] = [Library::JSymbolic, Library::Native];
25
26 pub fn extractors(self) -> &'static [Extractor] {
28 match self {
29 Self::JSymbolic => jsymbolic::JSYMBOLIC,
30 Self::Native => native::NATIVE,
31 }
32 }
33
34 pub fn name(self) -> &'static str {
36 match self {
37 Self::JSymbolic => "jsymbolic",
38 Self::Native => "native",
39 }
40 }
41}
42
43pub fn extractors_by_id(ids: &[&str], libraries: &[Library]) -> Vec<&'static Extractor> {
49 let wanted: Vec<String> = ids
50 .iter()
51 .map(|id| id.trim().to_lowercase().replace(['-', ' '], ""))
52 .collect();
53 let Some(first) = wanted.first() else {
54 return Vec::new();
55 };
56 let everything = first == "all";
57 libraries
58 .iter()
59 .flat_map(|library| library.extractors())
60 .filter(|extractor| everything || wanted.contains(&extractor.id().to_lowercase()))
61 .collect()
62}
63
64pub fn extractor_by_id(id: &str, libraries: &[Library]) -> Option<&'static Extractor> {
67 extractors_by_id(&[id], libraries).into_iter().next()
68}
69
70pub fn vector_by_id(stream: &Stream, id: &str) -> Result<Option<Vec<Value>>> {
77 let Some(extractor) = extractor_by_id(id, &Library::ALL) else {
78 return Ok(None);
79 };
80 let data = DataInstance::new(stream)?;
81 Ok(Some(extractor.extract(&data)?.values().to_vec()))
82}
83
84pub fn index_of(name: &str, library: Option<Library>) -> Option<(usize, Library)> {
92 let libraries: &[Library] = match library {
93 Some(Library::JSymbolic) => &[Library::JSymbolic],
94 Some(Library::Native) => &[Library::Native],
95 None => &Library::ALL,
96 };
97 libraries.iter().find_map(|library| {
98 library
99 .extractors()
100 .iter()
101 .position(|extractor| extractor.name() == name)
102 .map(|index| (index, *library))
103 })
104}
105
106pub fn all_features_as_list(stream: &Stream) -> Result<Vec<Vec<Value>>> {
114 let mut set = DataSet::new("");
115 set.add_extractors(Library::ALL.iter().flat_map(|library| library.extractors()));
116 set.add_data(stream, "", None)?;
117 set.process();
118 Ok(set.features()[0]
119 .iter()
120 .map(|feature| feature.values().to_vec())
121 .collect())
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127pub enum OutputFormat {
128 Tab,
131 Csv,
133 Arff,
135}
136
137impl OutputFormat {
138 pub fn from_name(name: &str) -> Option<Self> {
141 match name.to_lowercase().as_str() {
142 "tab" | "orange" | "taborange" => Some(Self::Tab),
143 "csv" | "comma" => Some(Self::Csv),
144 "arff" | "attribute" => Some(Self::Arff),
145 _ => None,
146 }
147 }
148
149 pub fn from_path(path: &str) -> Option<Self> {
152 path.rsplit_once('.')
153 .and_then(|(_, extension)| Self::from_name(extension))
154 }
155
156 pub fn extension(self) -> &'static str {
158 match self {
159 Self::Tab => ".tab",
160 Self::Csv => ".csv",
161 Self::Arff => ".arff",
162 }
163 }
164}
165
166#[derive(Clone, Debug, PartialEq)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169pub enum Cell {
170 Text(String),
172 Value(Value),
174}
175
176impl std::fmt::Display for Cell {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 match self {
181 Self::Text(text) => f.write_str(text),
182 Self::Value(value) => write!(f, "{value}"),
183 }
184 }
185}
186
187#[derive(Debug)]
189struct Entry {
190 data: DataInstance,
191 id: String,
192 class_value: String,
193}
194
195#[derive(Clone, Debug)]
198pub struct Failure {
199 pub row: usize,
201 pub extractor: &'static str,
203 pub error: Error,
205}
206
207#[derive(Debug)]
229pub struct DataSet {
230 class_label: String,
231 extractors: Vec<&'static Extractor>,
232 entries: Vec<Entry>,
233 features: Vec<Vec<Feature>>,
234 failures: Vec<Failure>,
235}
236
237impl DataSet {
238 pub fn new(class_label: impl Into<String>) -> Self {
241 Self {
242 class_label: class_label.into(),
243 extractors: Vec::new(),
244 entries: Vec::new(),
245 features: Vec::new(),
246 failures: Vec::new(),
247 }
248 }
249
250 pub fn class_label(&self) -> &str {
252 &self.class_label
253 }
254
255 pub fn extractors(&self) -> &[&'static Extractor] {
257 &self.extractors
258 }
259
260 pub fn add_extractors(&mut self, extractors: impl IntoIterator<Item = &'static Extractor>) {
263 self.extractors.extend(extractors);
264 }
265
266 pub fn add_data(
275 &mut self,
276 stream: &Stream,
277 class_value: impl Into<String>,
278 id: Option<&str>,
279 ) -> Result<()> {
280 let id = match id {
281 Some(id) => id.to_string(),
282 None => stream
283 .metadata()
284 .and_then(Metadata::title)
285 .unwrap_or_default(),
286 };
287 self.entries.push(Entry {
288 data: DataInstance::new(stream)?,
289 id: id.replace(' ', "_"),
290 class_value: class_value.into(),
291 });
292 Ok(())
293 }
294
295 pub fn len(&self) -> usize {
297 self.entries.len()
298 }
299
300 pub fn is_empty(&self) -> bool {
302 self.entries.is_empty()
303 }
304
305 pub fn process(&mut self) {
309 self.features.clear();
310 self.failures.clear();
311 for (row, entry) in self.entries.iter().enumerate() {
312 let mut features = Vec::with_capacity(self.extractors.len());
313 for extractor in &self.extractors {
314 features.push(match extractor.extract(&entry.data) {
315 Ok(feature) => feature,
316 Err(error) => {
317 self.failures.push(Failure {
318 row,
319 extractor: extractor.id(),
320 error,
321 });
322 extractor.blank()
323 }
324 });
325 }
326 self.features.push(features);
327 }
328 }
329
330 pub fn try_process(&mut self) -> Result<()> {
337 self.features.clear();
338 self.failures.clear();
339 for entry in &self.entries {
340 let features = self
341 .extractors
342 .iter()
343 .map(|extractor| extractor.extract(&entry.data))
344 .collect::<Result<Vec<_>>>()?;
345 self.features.push(features);
346 }
347 Ok(())
348 }
349
350 pub fn features(&self) -> &[Vec<Feature>] {
353 &self.features
354 }
355
356 pub fn failures(&self) -> &[Failure] {
358 &self.failures
359 }
360
361 pub fn attribute_labels(&self, include_class_label: bool, include_id: bool) -> Vec<String> {
364 let mut labels = Vec::new();
365 if include_id {
366 labels.push("Identifier".to_string());
367 }
368 for extractor in &self.extractors {
369 labels.extend(extractor.attribute_labels());
370 }
371 if include_class_label {
372 labels.push(self.class_label.replace(' ', "_"));
373 }
374 labels
375 }
376
377 pub fn discrete_labels(
380 &self,
381 include_class_label: bool,
382 include_id: bool,
383 ) -> Vec<Option<bool>> {
384 let mut labels = Vec::new();
385 if include_id {
386 labels.push(None);
387 }
388 for extractor in &self.extractors {
389 labels.extend(std::iter::repeat_n(
390 Some(extractor.discrete()),
391 extractor.dimensions(),
392 ));
393 }
394 if include_class_label {
395 labels.push(Some(true));
396 }
397 labels
398 }
399
400 pub fn class_position_labels(
403 &self,
404 include_id: bool,
405 include_class_label: bool,
406 ) -> Vec<Option<bool>> {
407 let mut labels = Vec::new();
408 if include_id {
409 labels.push(None);
410 }
411 for extractor in &self.extractors {
412 labels.extend(std::iter::repeat_n(Some(false), extractor.dimensions()));
413 }
414 if include_class_label {
415 labels.push(Some(true));
416 }
417 labels
418 }
419
420 pub fn features_as_list(&self, include_class_label: bool, include_id: bool) -> Vec<Vec<Cell>> {
423 self.entries
424 .iter()
425 .zip(&self.features)
426 .map(|(entry, features)| {
427 let mut row = Vec::new();
428 if include_id {
429 row.push(Cell::Text(entry.id.clone()));
430 }
431 for feature in features {
432 row.extend(feature.values().iter().copied().map(Cell::Value));
433 }
434 if include_class_label {
435 row.push(Cell::Text(entry.class_value.clone()));
436 }
437 row
438 })
439 .collect()
440 }
441
442 pub fn unique_class_values(&self) -> Vec<&str> {
445 let mut values: Vec<&str> = Vec::new();
446 for entry in &self.entries {
447 if !values.contains(&entry.class_value.as_str()) {
448 values.push(&entry.class_value);
449 }
450 }
451 values
452 }
453
454 pub fn to_text(&self, format: OutputFormat) -> String {
457 let rows = self.features_as_list(true, true);
458 let joined = |row: &[Cell], separator: &str| -> String {
459 row.iter()
460 .map(ToString::to_string)
461 .collect::<Vec<_>>()
462 .join(separator)
463 };
464 let mut lines: Vec<String> = Vec::new();
465 match format {
466 OutputFormat::Tab => {
467 lines.push(self.attribute_labels(true, true).join("\t"));
468 lines.push(
469 self.discrete_labels(true, true)
470 .into_iter()
471 .map(|discrete| match discrete {
472 None => "string",
473 Some(true) => "discrete",
474 Some(false) => "continuous",
475 })
476 .collect::<Vec<_>>()
477 .join("\t"),
478 );
479 lines.push(
480 self.class_position_labels(true, true)
481 .into_iter()
482 .map(|class| match class {
483 None => "meta",
484 Some(true) => "class",
485 Some(false) => "",
486 })
487 .collect::<Vec<_>>()
488 .join("\t"),
489 );
490 lines.extend(rows.iter().map(|row| joined(row, "\t")));
491 }
492 OutputFormat::Csv => {
493 lines.push(self.attribute_labels(true, true).join(","));
494 lines.extend(rows.iter().map(|row| joined(row, ",")));
495 }
496 OutputFormat::Arff => {
497 lines.push(format!("@RELATION {}", self.class_label));
498 let labels = self.attribute_labels(true, true);
499 let discrete = self.discrete_labels(true, true);
500 let classes = self.class_position_labels(true, true);
501 for ((label, discrete), class) in labels.iter().zip(discrete).zip(classes) {
502 lines.push(match (class, discrete) {
503 (Some(true), _) => {
504 format!(
505 "@ATTRIBUTE class {{{}}}",
506 self.unique_class_values().join(",")
507 )
508 }
509 (_, None) => format!("@ATTRIBUTE {label} STRING"),
510 _ => format!("@ATTRIBUTE {label} NUMERIC"),
511 });
512 }
513 lines.push("@DATA".to_string());
514 lines.extend(rows.iter().map(|row| joined(row, ",")));
515 }
516 }
517 lines.join("\n")
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use crate::tinynotation::from_tiny_notation;
525
526 fn two_pieces() -> Result<DataSet> {
527 let line = from_tiny_notation("4/4 c4 d e2")?;
528 let mut set = DataSet::new("Composer");
529 set.add_extractors(extractors_by_id(&["R31"], &[Library::JSymbolic]));
530 set.add_extractors(extractors_by_id(&["P22"], &[Library::Native]));
531 set.add_extractors(extractors_by_id(&["R35"], &[Library::JSymbolic]));
532 set.add_data(&line, "Bach", Some("my piece"))?;
533 set.add_data(&line, "Handel", None)?;
534 set.process();
535 Ok(set)
536 }
537
538 #[test]
539 fn a_data_set_is_written_as_music21_writes_one() -> Result<()> {
540 let set = two_pieces()?;
542 assert_eq!(
543 set.to_text(OutputFormat::Tab),
544 "Identifier\tInitial_Time_Signature_0\tInitial_Time_Signature_1\tQuality\t\
545 Changes_of_Meter\tComposer\nstring\tdiscrete\tdiscrete\tdiscrete\tdiscrete\t\
546 discrete\nmeta\t\t\t\t\tclass\nmy_piece\t4\t4\t1\t0\tBach\n\t4\t4\t1\t0\tHandel"
547 );
548 assert_eq!(
549 set.to_text(OutputFormat::Csv),
550 "Identifier,Initial_Time_Signature_0,Initial_Time_Signature_1,Quality,\
551 Changes_of_Meter,Composer\nmy_piece,4,4,1,0,Bach\n,4,4,1,0,Handel"
552 );
553 assert_eq!(
554 set.to_text(OutputFormat::Arff),
555 "@RELATION Composer\n@ATTRIBUTE Identifier STRING\n\
556 @ATTRIBUTE Initial_Time_Signature_0 NUMERIC\n\
557 @ATTRIBUTE Initial_Time_Signature_1 NUMERIC\n@ATTRIBUTE Quality NUMERIC\n\
558 @ATTRIBUTE Changes_of_Meter NUMERIC\n@ATTRIBUTE class {Bach,Handel}\n@DATA\n\
559 my_piece,4,4,1,0,Bach\n,4,4,1,0,Handel"
560 );
561 Ok(())
562 }
563
564 #[test]
565 fn extractors_are_found_by_id_and_by_name() {
566 let ids = |found: Vec<&'static Extractor>| -> Vec<&str> {
567 found.iter().map(|extractor| extractor.id()).collect()
568 };
569 assert_eq!(
570 ids(extractors_by_id(&["p22", "ql1", "P-20"], &Library::ALL)),
571 ["P20", "P22", "P22", "QL1"]
572 );
573 assert_eq!(ids(extractors_by_id(&["p 21"], &Library::ALL)), ["P21"]);
574 let all = extractors_by_id(&["all"], &Library::ALL);
575 let expected = if cfg!(feature = "language-detection") {
577 92
578 } else {
579 91
580 };
581 assert_eq!(all.len(), expected);
582 assert_eq!(index_of("Range", None), Some((61, Library::JSymbolic)));
583 assert_eq!(
584 index_of("Ends With Landini Melodic Contour", None),
585 Some((18, Library::Native))
586 );
587 assert_eq!(
588 index_of("Tonal Certainty", Some(Library::Native)),
589 Some((1, Library::Native))
590 );
591 assert_eq!(index_of("aBrandNewFeature!", None), None);
592 assert_eq!(OutputFormat::from_path("test.tab"), Some(OutputFormat::Tab));
593 assert_eq!(OutputFormat::from_path("junk"), None);
594 }
595
596 #[test]
597 fn every_feature_of_a_piece_is_listed() -> Result<()> {
598 let features = all_features_as_list(&from_tiny_notation("4/4 c4 d e2")?)?;
600 let expected = if cfg!(feature = "language-detection") {
601 92
602 } else {
603 91
604 };
605 assert_eq!(features.len(), expected);
606 assert_eq!(
607 features[2..5],
608 [
609 vec![Value::Integer(2)],
610 vec![Value::Integer(2)],
611 vec![Value::Float(1.0)]
612 ]
613 );
614 Ok(())
615 }
616}