1use super::{DataInstance, Extractor, Value};
19use crate::stream::StreamElement;
20use crate::{
21 defaults::{FloatType, IntegerType},
22 error::{Error, Result},
23};
24
25pub const JSYMBOLIC: &[Extractor] = &[
27 Extractor {
28 id: "M1",
29 name: "Melodic Interval Histogram",
30 description: "A features array with bins corresponding to the values of the melodic interval histogram.",
31 dimensions: 128,
32 discrete: true,
33 normalize: true,
34 process: melodic_interval_histogram,
35 },
36 Extractor {
37 id: "M2",
38 name: "Average Melodic Interval",
39 description: "Average melodic interval (in semitones).",
40 dimensions: 1,
41 discrete: true,
42 normalize: false,
43 process: average_melodic_interval,
44 },
45 Extractor {
46 id: "M3",
47 name: "Most Common Melodic Interval",
48 description: "Melodic interval with the highest frequency.",
49 dimensions: 1,
50 discrete: true,
51 normalize: false,
52 process: most_common_melodic_interval,
53 },
54 Extractor {
55 id: "M4",
56 name: "Distance Between Most Common Melodic Intervals",
57 description: "Absolute value of the difference between the most common melodic interval and the second most common melodic interval.",
58 dimensions: 1,
59 discrete: true,
60 normalize: false,
61 process: distance_between_most_common_melodic_intervals,
62 },
63 Extractor {
64 id: "M5",
65 name: "Most Common Melodic Interval Prevalence",
66 description: "Fraction of melodic intervals that belong to the most common interval.",
67 dimensions: 1,
68 discrete: true,
69 normalize: false,
70 process: most_common_melodic_interval_prevalence,
71 },
72 Extractor {
73 id: "M6",
74 name: "Relative Strength of Most Common Intervals",
75 description: "Fraction of melodic intervals that belong to the second most common interval divided by the fraction of melodic intervals belonging to the most common interval.",
76 dimensions: 1,
77 discrete: true,
78 normalize: false,
79 process: relative_strength_of_most_common_intervals,
80 },
81 Extractor {
82 id: "M7",
83 name: "Number of Common Melodic Intervals",
84 description: "Number of melodic intervals that represent at least 9% of all melodic intervals.",
85 dimensions: 1,
86 discrete: true,
87 normalize: false,
88 process: number_of_common_melodic_intervals,
89 },
90 Extractor {
91 id: "M8",
92 name: "Amount of Arpeggiation",
93 description: "Fraction of horizontal intervals that are repeated notes, minor thirds, major thirds, perfect fifths, minor sevenths, major sevenths, octaves, minor tenths or major tenths.",
94 dimensions: 1,
95 discrete: true,
96 normalize: false,
97 process: amount_of_arpeggiation,
98 },
99 Extractor {
100 id: "M9",
101 name: "Repeated Notes",
102 description: "Fraction of notes that are repeated melodically.",
103 dimensions: 1,
104 discrete: true,
105 normalize: false,
106 process: repeated_notes,
107 },
108 Extractor {
109 id: "m10",
110 name: "Chromatic Motion",
111 description: "Fraction of melodic intervals corresponding to a semi-tone.",
112 dimensions: 1,
113 discrete: true,
114 normalize: false,
115 process: chromatic_motion,
116 },
117 Extractor {
118 id: "M11",
119 name: "Stepwise Motion",
120 description: "Fraction of melodic intervals that corresponded to a minor or major second.",
121 dimensions: 1,
122 discrete: true,
123 normalize: false,
124 process: stepwise_motion,
125 },
126 Extractor {
127 id: "M12",
128 name: "Melodic Thirds",
129 description: "Fraction of melodic intervals that are major or minor thirds.",
130 dimensions: 1,
131 discrete: true,
132 normalize: false,
133 process: melodic_thirds,
134 },
135 Extractor {
136 id: "M13",
137 name: "Melodic Fifths",
138 description: "Fraction of melodic intervals that are perfect fifths.",
139 dimensions: 1,
140 discrete: true,
141 normalize: false,
142 process: melodic_fifths,
143 },
144 Extractor {
145 id: "M14",
146 name: "Melodic Tritones",
147 description: "Fraction of melodic intervals that are tritones.",
148 dimensions: 1,
149 discrete: true,
150 normalize: false,
151 process: melodic_tritones,
152 },
153 Extractor {
154 id: "M15",
155 name: "Melodic Octaves",
156 description: "Fraction of melodic intervals that are octaves.",
157 dimensions: 1,
158 discrete: true,
159 normalize: false,
160 process: melodic_octaves,
161 },
162 Extractor {
163 id: "m17",
164 name: "Direction of Motion",
165 description: "Fraction of melodic intervals that are rising rather than falling.",
166 dimensions: 1,
167 discrete: true,
168 normalize: false,
169 process: direction_of_motion,
170 },
171 Extractor {
172 id: "M18",
173 name: "Duration of Melodic Arcs",
174 description: "Average number of notes that separate melodic peaks and troughs in any part. This is calculated as the total number of intervals (not counting unisons) divided by the number of times the melody changes direction.",
175 dimensions: 1,
176 discrete: true,
177 normalize: false,
178 process: duration_of_melodic_arcs,
179 },
180 Extractor {
181 id: "M19",
182 name: "Size of Melodic Arcs",
183 description: "Average span (in semitones) between melodic peaks and troughs in any part. Each time the melody changes direction begins a new arc. The average size ofmelodic arcs is defined as the total size of melodicintervals between changes of directions - or betweenthe start of the melody and the first change ofdirection - divided by the number of direction changes.",
184 dimensions: 1,
185 discrete: true,
186 normalize: false,
187 process: size_of_melodic_arcs,
188 },
189 Extractor {
190 id: "I1",
191 name: "Pitched Instruments Present",
192 description: "Which pitched General MIDI Instruments are present. There is one entry for each instrument, which is set to 1.0 if there is at least one Note On in the recording corresponding to the instrument and to 0.0 if there is not.",
193 dimensions: 128,
194 discrete: true,
195 normalize: false,
196 process: pitched_instruments_present,
197 },
198 Extractor {
199 id: "I3",
200 name: "Note Prevalence of Pitched Instruments",
201 description: "The fraction of (pitched) notes played by each General MIDI Instrument. There is one entry for each instrument, which is set to the number of Note Ons played using the corresponding MIDI patch divided by the total number of Note Ons in the recording.",
202 dimensions: 128,
203 discrete: true,
204 normalize: false,
205 process: note_prevalence_of_pitched_instruments,
206 },
207 Extractor {
208 id: "I6",
209 name: "Variability of Note Prevalence of Pitched Instruments",
210 description: "Standard deviation of the fraction of Note Ons played by each (pitched) General MIDI instrument that is used to play at least one note.",
211 dimensions: 1,
212 discrete: true,
213 normalize: false,
214 process: variability_of_note_prevalence_of_pitched_instruments,
215 },
216 Extractor {
217 id: "I8",
218 name: "Number of Pitched Instruments",
219 description: "Total number of General MIDI patches that are used to play at least one note.",
220 dimensions: 1,
221 discrete: true,
222 normalize: false,
223 process: number_of_pitched_instruments,
224 },
225 Extractor {
226 id: "I11",
227 name: "String Keyboard Fraction",
228 description: "Fraction of all Note Ons belonging to string keyboard patches (General MIDI patches 1 to 8).",
229 dimensions: 1,
230 discrete: true,
231 normalize: false,
232 process: string_keyboard_fraction,
233 },
234 Extractor {
235 id: "I12",
236 name: "Acoustic Guitar Fraction",
237 description: "Fraction of all Note Ons belonging to acoustic guitar patches (General MIDI patches 25 and 26).",
238 dimensions: 1,
239 discrete: true,
240 normalize: false,
241 process: acoustic_guitar_fraction,
242 },
243 Extractor {
244 id: "I13",
245 name: "Electric Guitar Fraction",
246 description: "Fraction of all Note Ons belonging to electric guitar patches (General MIDI patches 27 to 32).",
247 dimensions: 1,
248 discrete: true,
249 normalize: false,
250 process: electric_guitar_fraction,
251 },
252 Extractor {
253 id: "I14",
254 name: "Violin Fraction",
255 description: "Fraction of all Note Ons belonging to violin patches (General MIDI patches 41 or 111).",
256 dimensions: 1,
257 discrete: true,
258 normalize: false,
259 process: violin_fraction,
260 },
261 Extractor {
262 id: "I15",
263 name: "Saxophone Fraction",
264 description: "Fraction of all Note Ons belonging to saxophone patches (General MIDI patches 65 through 68).",
265 dimensions: 1,
266 discrete: true,
267 normalize: false,
268 process: saxophone_fraction,
269 },
270 Extractor {
271 id: "I16",
272 name: "Brass Fraction",
273 description: "Fraction of all Note Ons belonging to brass patches (General MIDI patches 57 through 68).",
274 dimensions: 1,
275 discrete: true,
276 normalize: false,
277 process: brass_fraction,
278 },
279 Extractor {
280 id: "I17",
281 name: "Woodwinds Fraction",
282 description: "Fraction of all Note Ons belonging to woodwind patches (General MIDI patches 69 through 76).",
283 dimensions: 1,
284 discrete: true,
285 normalize: false,
286 process: woodwinds_fraction,
287 },
288 Extractor {
289 id: "I18",
290 name: "Orchestral Strings Fraction",
291 description: "Fraction of all Note Ons belonging to orchestral strings patches (General MIDI patches 41 or 47).",
292 dimensions: 1,
293 discrete: true,
294 normalize: false,
295 process: orchestral_strings_fraction,
296 },
297 Extractor {
298 id: "I19",
299 name: "String Ensemble Fraction",
300 description: "Fraction of all Note Ons belonging to string ensemble patches (General MIDI patches 49 to 52).",
301 dimensions: 1,
302 discrete: true,
303 normalize: false,
304 process: string_ensemble_fraction,
305 },
306 Extractor {
307 id: "I20",
308 name: "Electric Instrument Fraction",
309 description: "Fraction of all Note Ons belonging to electric instrument patches (General MIDI patches 5, 6, 17, 19, 27 to 32 or 34 to 40).",
310 dimensions: 1,
311 discrete: true,
312 normalize: false,
313 process: electric_instrument_fraction,
314 },
315 Extractor {
316 id: "R15",
317 name: "Note Density",
318 description: "Average number of notes per second.",
319 dimensions: 1,
320 discrete: true,
321 normalize: false,
322 process: note_density,
323 },
324 Extractor {
325 id: "R17",
326 name: "Average Note Duration",
327 description: "Average duration of notes in seconds.",
328 dimensions: 1,
329 discrete: true,
330 normalize: false,
331 process: average_note_duration,
332 },
333 Extractor {
334 id: "R18",
335 name: "Variability of Note Duration",
336 description: "Standard deviation of note durations in seconds.",
337 dimensions: 1,
338 discrete: true,
339 normalize: false,
340 process: variability_of_note_duration,
341 },
342 Extractor {
343 id: "R19",
344 name: "Maximum Note Duration",
345 description: "Duration of the longest note (in seconds).",
346 dimensions: 1,
347 discrete: true,
348 normalize: false,
349 process: maximum_note_duration,
350 },
351 Extractor {
352 id: "R20",
353 name: "Minimum Note Duration",
354 description: "Duration of the shortest note (in seconds).",
355 dimensions: 1,
356 discrete: true,
357 normalize: false,
358 process: minimum_note_duration,
359 },
360 Extractor {
361 id: "R21",
362 name: "Staccato Incidence",
363 description: "Number of notes with durations of less than a 10th of a second divided by the total number of notes in the recording.",
364 dimensions: 1,
365 discrete: true,
366 normalize: false,
367 process: staccato_incidence,
368 },
369 Extractor {
370 id: "R22",
371 name: "Average Time Between Attacks",
372 description: "Average time in seconds between Note On events (regardless of channel).",
373 dimensions: 1,
374 discrete: true,
375 normalize: false,
376 process: average_time_between_attacks,
377 },
378 Extractor {
379 id: "R23",
380 name: "Variability of Time Between Attacks",
381 description: "Standard deviation of the times, in seconds, between Note On events (regardless of channel).",
382 dimensions: 1,
383 discrete: true,
384 normalize: false,
385 process: variability_of_time_between_attacks,
386 },
387 Extractor {
388 id: "R24",
389 name: "Average Time Between Attacks For Each Voice",
390 description: "Average of average times in seconds between Note On events on individual channels that contain at least one note.",
391 dimensions: 1,
392 discrete: true,
393 normalize: false,
394 process: average_time_between_attacks_for_each_voice,
395 },
396 Extractor {
397 id: "R25",
398 name: "Average Variability of Time Between Attacks For Each Voice",
399 description: "Average standard deviation, in seconds, of time between Note On events on individual channels that contain at least one note.",
400 dimensions: 1,
401 discrete: true,
402 normalize: false,
403 process: average_variability_of_time_between_attacks_for_each_voice,
404 },
405 Extractor {
406 id: "R30",
407 name: "Initial Tempo",
408 description: "Tempo in beats per minute at the start of the recording.",
409 dimensions: 1,
410 discrete: true,
411 normalize: false,
412 process: initial_tempo,
413 },
414 Extractor {
415 id: "R31",
416 name: "Initial Time Signature",
417 description: "A feature array with two elements. The first is the numerator of the first occurring time signature and the second is the denominator of the first occurring time signature. Both are set to 0 if no time signature is present.",
418 dimensions: 2,
419 discrete: true,
420 normalize: false,
421 process: initial_time_signature,
422 },
423 Extractor {
424 id: "R32",
425 name: "Compound Or Simple Meter",
426 description: "Set to 1 if the initial meter is compound (numerator of time signature is greater than or equal to 6 and is evenly divisible by 3) and to 0 if it is simple (if the above condition is not fulfilled).",
427 dimensions: 1,
428 discrete: true,
429 normalize: false,
430 process: compound_or_simple_meter,
431 },
432 Extractor {
433 id: "R33",
434 name: "Triple Meter",
435 description: "Set to 1 if numerator of initial time signature is 3, set to 0 otherwise.",
436 dimensions: 1,
437 discrete: true,
438 normalize: false,
439 process: triple_meter,
440 },
441 Extractor {
442 id: "R34",
443 name: "Quintuple Meter",
444 description: "Set to 1 if numerator of initial time signature is 5, set to 0 otherwise.",
445 dimensions: 1,
446 discrete: true,
447 normalize: false,
448 process: quintuple_meter,
449 },
450 Extractor {
451 id: "R35",
452 name: "Changes of Meter",
453 description: "Set to 1 if the time signature is changed one or more times during the recording.",
454 dimensions: 1,
455 discrete: true,
456 normalize: false,
457 process: changes_of_meter,
458 },
459 Extractor {
460 id: "R36",
461 name: "Duration",
462 description: "The total duration in seconds of the music.",
463 dimensions: 1,
464 discrete: false,
465 normalize: false,
466 process: duration,
467 },
468 Extractor {
469 id: "T1",
470 name: "Maximum Number of Independent Voices",
471 description: "Maximum number of different channels in which notes have sounded simultaneously. Here, Parts are treated as channels.",
472 dimensions: 1,
473 discrete: true,
474 normalize: false,
475 process: maximum_number_of_independent_voices,
476 },
477 Extractor {
478 id: "T2",
479 name: "Average Number of Independent Voices",
480 description: "Average number of different channels in which notes have sounded simultaneously. Rests are not included in this calculation. Here, Parts are treated as voices.",
481 dimensions: 1,
482 discrete: true,
483 normalize: false,
484 process: average_number_of_independent_voices,
485 },
486 Extractor {
487 id: "T3",
488 name: "Variability of Number of Independent Voices",
489 description: "Standard deviation of number of different channels in which notes have sounded simultaneously. Rests are not included in this calculation.",
490 dimensions: 1,
491 discrete: true,
492 normalize: false,
493 process: variability_of_number_of_independent_voices,
494 },
495 Extractor {
496 id: "P1",
497 name: "Most Common Pitch Prevalence",
498 description: "Fraction of Note Ons corresponding to the most common pitch.",
499 dimensions: 1,
500 discrete: false,
501 normalize: false,
502 process: most_common_pitch_prevalence,
503 },
504 Extractor {
505 id: "P2",
506 name: "Most Common Pitch Class Prevalence",
507 description: "Fraction of Note Ons corresponding to the most common pitch class.",
508 dimensions: 1,
509 discrete: false,
510 normalize: false,
511 process: most_common_pitch_class_prevalence,
512 },
513 Extractor {
514 id: "P3",
515 name: "Relative Strength of Top Pitches",
516 description: "The frequency of the 2nd most common pitch divided by the frequency of the most common pitch.",
517 dimensions: 1,
518 discrete: false,
519 normalize: false,
520 process: relative_strength_of_top_pitches,
521 },
522 Extractor {
523 id: "P4",
524 name: "Relative Strength of Top Pitch Classes",
525 description: "The frequency of the 2nd most common pitch class divided by the frequency of the most common pitch class.",
526 dimensions: 1,
527 discrete: false,
528 normalize: false,
529 process: relative_strength_of_top_pitch_classes,
530 },
531 Extractor {
532 id: "P5",
533 name: "Interval Between Strongest Pitches",
534 description: "Absolute value of the difference between the pitches of the two most common MIDI pitches.",
535 dimensions: 1,
536 discrete: true,
537 normalize: false,
538 process: interval_between_strongest_pitches,
539 },
540 Extractor {
541 id: "P6",
542 name: "Interval Between Strongest Pitch Classes",
543 description: "Absolute value of the difference between the pitch classes of the two most common MIDI pitch classes.",
544 dimensions: 1,
545 discrete: true,
546 normalize: false,
547 process: interval_between_strongest_pitch_classes,
548 },
549 Extractor {
550 id: "P7",
551 name: "Number of Common Pitches",
552 description: "Number of pitches that account individually for at least 9% of all notes.",
553 dimensions: 1,
554 discrete: true,
555 normalize: false,
556 process: number_of_common_pitches,
557 },
558 Extractor {
559 id: "P8",
560 name: "Pitch Variety",
561 description: "Number of pitches used at least once.",
562 dimensions: 1,
563 discrete: true,
564 normalize: false,
565 process: pitch_variety,
566 },
567 Extractor {
568 id: "P9",
569 name: "Pitch Class Variety",
570 description: "Number of pitch classes used at least once.",
571 dimensions: 1,
572 discrete: true,
573 normalize: false,
574 process: pitch_class_variety,
575 },
576 Extractor {
577 id: "P10",
578 name: "Range",
579 description: "Difference between highest and lowest pitches.",
580 dimensions: 1,
581 discrete: true,
582 normalize: false,
583 process: range,
584 },
585 Extractor {
586 id: "P11",
587 name: "Most Common Pitch",
588 description: "Bin label of the most common pitch.",
589 dimensions: 1,
590 discrete: false,
591 normalize: false,
592 process: most_common_pitch,
593 },
594 Extractor {
595 id: "P12",
596 name: "Primary Register",
597 description: "Average MIDI pitch.",
598 dimensions: 1,
599 discrete: false,
600 normalize: false,
601 process: primary_register,
602 },
603 Extractor {
604 id: "P13",
605 name: "Importance of Bass Register",
606 description: "Fraction of Note Ons between MIDI pitches 0 and 54.",
607 dimensions: 1,
608 discrete: false,
609 normalize: false,
610 process: importance_of_bass_register,
611 },
612 Extractor {
613 id: "P14",
614 name: "Importance of Middle Register",
615 description: "Fraction of Note Ons between MIDI pitches 55 and 72.",
616 dimensions: 1,
617 discrete: false,
618 normalize: false,
619 process: importance_of_middle_register,
620 },
621 Extractor {
622 id: "P15",
623 name: "Importance of High Register",
624 description: "Fraction of Note Ons between MIDI pitches 73 and 127.",
625 dimensions: 1,
626 discrete: false,
627 normalize: false,
628 process: importance_of_high_register,
629 },
630 Extractor {
631 id: "P16",
632 name: "Most Common Pitch Class",
633 description: "Bin label of the most common pitch class.",
634 dimensions: 1,
635 discrete: true,
636 normalize: false,
637 process: most_common_pitch_class,
638 },
639 Extractor {
640 id: "P19",
641 name: "Basic Pitch Histogram",
642 description: "A features array with bins corresponding to the values of the basic pitch histogram.",
643 dimensions: 128,
644 discrete: true,
645 normalize: true,
646 process: basic_pitch_histogram,
647 },
648 Extractor {
649 id: "P20",
650 name: "Pitch Class Distribution",
651 description: "A feature array with 12 entries where the first holds the frequency of the bin of the pitch class histogram with the highest frequency, and the following entries holding the successive bins of the histogram, wrapping around if necessary.",
652 dimensions: 12,
653 discrete: false,
654 normalize: true,
655 process: pitch_class_distribution,
656 },
657 Extractor {
658 id: "P21",
659 name: "Fifths Pitch Histogram",
660 description: "A feature array with bins corresponding to the values of the 5ths pitch class histogram.",
661 dimensions: 12,
662 discrete: true,
663 normalize: true,
664 process: fifths_pitch_histogram,
665 },
666 Extractor {
667 id: "P22",
668 name: "Quality",
669 description: "Set to 0 if the key signature indicates that a recording is major, set to 1 if it indicates that it is minor and set to 0 if key signature is unknown.",
670 dimensions: 1,
671 discrete: true,
672 normalize: false,
673 process: quality,
674 },
675];
676
677pub fn extractor(id: &str) -> Option<&'static Extractor> {
679 JSYMBOLIC.iter().find(|extractor| extractor.id == id)
680}
681
682fn lacks_notes() -> Error {
683 Error::Feature("input lacks notes".to_string())
684}
685
686fn first_max(values: &[usize]) -> usize {
689 let most = values.iter().copied().max().unwrap_or(0);
690 values.iter().position(|value| *value == most).unwrap_or(0)
691}
692
693fn most_common(counts: &[(IntegerType, usize)]) -> Vec<(IntegerType, usize)> {
696 let mut sorted = counts.to_vec();
697 sorted.sort_by_key(|entry| std::cmp::Reverse(entry.1));
698 sorted
699}
700
701fn share_of(histogram: &[usize], targets: &[usize]) -> Result<FloatType> {
703 let total: usize = histogram.iter().sum();
704 if total == 0 {
705 return Err(lacks_notes());
706 }
707 let count: usize = targets.iter().map(|target| histogram[*target]).sum();
708 Ok(count as FloatType / total as FloatType)
709}
710
711fn melodic_interval_histogram(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
712 for (value, count) in vector.iter_mut().zip(data.midi_interval_histogram()) {
713 *value = (*count).into();
714 }
715 Ok(())
716}
717
718fn average_melodic_interval(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
719 let histogram = data.midi_interval_histogram();
720 let count: usize = histogram.iter().sum();
721 if count == 0 {
722 return Err(lacks_notes());
723 }
724 let total: usize = histogram
725 .iter()
726 .enumerate()
727 .map(|(size, count)| size * count)
728 .sum();
729 vector[0] = (total as FloatType / count as FloatType).into();
730 Ok(())
731}
732
733fn most_common_melodic_interval(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
734 vector[0] = first_max(data.midi_interval_histogram()).into();
735 Ok(())
736}
737
738fn distance_between_most_common_melodic_intervals(
739 data: &DataInstance,
740 vector: &mut [Value],
741) -> Result<()> {
742 let mut histogram = data.midi_interval_histogram().to_vec();
743 let first = first_max(&histogram);
744 histogram[first] = 0;
745 let second = first_max(&histogram);
746 vector[0] = first.abs_diff(second).into();
747 Ok(())
748}
749
750fn most_common_melodic_interval_prevalence(
751 data: &DataInstance,
752 vector: &mut [Value],
753) -> Result<()> {
754 let histogram = data.midi_interval_histogram();
755 let most = histogram.iter().copied().max().unwrap_or(0);
756 let count: usize = histogram.iter().sum();
757 if count == 0 {
758 return Err(lacks_notes());
759 }
760 vector[0] = (most as FloatType / count as FloatType).into();
761 Ok(())
762}
763
764fn relative_strength_of_most_common_intervals(
765 data: &DataInstance,
766 vector: &mut [Value],
767) -> Result<()> {
768 let mut histogram = data.midi_interval_histogram().to_vec();
769 let count: usize = histogram.iter().sum();
770 if count == 0 {
771 return Err(lacks_notes());
772 }
773 let first = first_max(&histogram);
774 let most = histogram[first];
775 histogram[first] = 0;
776 let second = histogram.iter().copied().max().unwrap_or(0);
777 let count = count as FloatType;
778 vector[0] = ((second as FloatType / count) / (most as FloatType / count)).into();
779 Ok(())
780}
781
782fn number_of_common_melodic_intervals(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
783 let histogram = data.midi_interval_histogram();
784 let total: usize = histogram.iter().sum();
785 if total == 0 {
786 return Err(lacks_notes());
787 }
788 vector[0] = histogram
789 .iter()
790 .filter(|count| **count as FloatType / total as FloatType >= 0.09)
791 .count()
792 .into();
793 Ok(())
794}
795
796fn amount_of_arpeggiation(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
797 let histogram = data.midi_interval_histogram();
798 let total: usize = histogram.iter().sum();
799 if total == 0 {
800 return Ok(());
801 }
802 let count: usize = [0, 3, 4, 7, 10, 11, 12, 15, 16]
803 .iter()
804 .map(|target| histogram[*target])
805 .sum();
806 if count == 0 {
809 return Err(lacks_notes());
810 }
811 vector[0] = (count as FloatType / total as FloatType).into();
812 Ok(())
813}
814
815fn repeated_notes(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
816 let histogram = data.midi_interval_histogram();
817 let total: usize = histogram.iter().sum();
818 if total == 0 {
819 return Ok(());
820 }
821 vector[0] = (histogram[0] as FloatType / total as FloatType).into();
822 Ok(())
823}
824
825fn chromatic_motion(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
826 vector[0] = share_of(data.midi_interval_histogram(), &[1])?.into();
827 Ok(())
828}
829
830fn stepwise_motion(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
831 vector[0] = share_of(data.midi_interval_histogram(), &[1, 2])?.into();
832 Ok(())
833}
834
835fn melodic_thirds(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
836 vector[0] = share_of(data.midi_interval_histogram(), &[3, 4])?.into();
837 Ok(())
838}
839
840fn melodic_fifths(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
841 vector[0] = share_of(data.midi_interval_histogram(), &[7])?.into();
842 Ok(())
843}
844
845fn melodic_tritones(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
846 vector[0] = share_of(data.midi_interval_histogram(), &[6])?.into();
847 Ok(())
848}
849
850fn melodic_octaves(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
851 vector[0] = share_of(
852 data.midi_interval_histogram(),
853 &[12, 24, 48, 60, 72, 84, 96, 108, 120],
854 )?
855 .into();
856 Ok(())
857}
858
859fn contours(data: &DataInstance) -> Vec<&[IntegerType]> {
862 if data.parts_count() > 0 {
863 data.parts()[..data.parts_count()]
864 .iter()
865 .map(DataInstance::contour_list)
866 .collect()
867 } else {
868 vec![data.contour_list()]
869 }
870}
871
872fn direction_of_motion(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
873 let (mut rising, mut falling) = (0usize, 0usize);
874 for contour in contours(data) {
875 for step in contour {
876 if *step > 0 {
877 rising += 1;
878 } else if *step < 0 {
879 falling += 1;
880 }
881 }
882 }
883 if rising + falling == 0 {
884 return Err(lacks_notes());
885 }
886 vector[0] = (rising as FloatType / (falling + rising) as FloatType).into();
887 Ok(())
888}
889
890fn duration_of_melodic_arcs(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
891 let (mut changes, mut moving) = (0usize, 0usize);
892 for contour in contours(data) {
893 let mut direction = 0;
894 for step in contour {
895 if *step != 0 {
896 moving += 1;
897 }
898 match direction {
899 1 if *step < 0 => {
900 changes += 1;
901 direction = -1;
902 }
903 -1 if *step > 0 => {
904 changes += 1;
905 direction = 1;
906 }
907 0 => direction = step.signum(),
908 _ => {}
909 }
910 }
911 }
912 if changes > 0 {
913 vector[0] = (moving as FloatType / changes as FloatType).into();
914 }
915 Ok(())
916}
917
918fn size_of_melodic_arcs(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
919 let (mut changes, mut total) = (0usize, 0i64);
920 for contour in contours(data) {
921 let mut direction = 0;
922 let mut arc = 0i64;
923 for step in contour {
924 let size = i64::from(step.abs());
925 match direction {
926 1 if *step > 0 => arc += size,
927 -1 if *step < 0 => arc += size,
928 1 | -1 if *step != 0 => {
929 total += arc;
930 changes += 1;
931 direction = step.signum();
932 arc = size;
933 }
934 0 if *step != 0 => {
935 direction = step.signum();
936 arc += size;
937 }
938 _ => {}
939 }
940 }
941 }
942 if changes > 0 {
943 vector[0] = (total as FloatType / changes as FloatType).into();
944 }
945 Ok(())
946}
947
948fn total_of(counts: &[(IntegerType, usize)]) -> usize {
949 counts.iter().map(|(_, count)| count).sum()
950}
951
952fn most_common_pitch_prevalence(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
953 let histogram = data.midi_pitch_histogram();
954 if histogram.is_empty() {
955 return Err(lacks_notes());
956 }
957 let most = histogram.iter().map(|(_, count)| *count).max().unwrap_or(0);
958 vector[0] = (most as FloatType / total_of(histogram) as FloatType).into();
959 Ok(())
960}
961
962fn most_common_pitch_class_prevalence(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
963 let histogram = data.pitch_class_histogram();
964 let class = first_max(histogram);
965 let total: usize = histogram.iter().sum();
966 if total == 0 {
967 return Err(lacks_notes());
968 }
969 vector[0] = (histogram[class] as FloatType / total as FloatType).into();
970 Ok(())
971}
972
973fn relative_strength_of_top_pitches(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
974 vector[0] = match most_common(data.midi_pitch_histogram()).as_slice() {
975 [first, second, ..] => (second.1 as FloatType / first.1 as FloatType).into(),
976 _ => Value::Float(0.0),
977 };
978 Ok(())
979}
980
981fn relative_strength_of_top_pitch_classes(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
982 let mut histogram = *data.pitch_class_histogram();
983 let first = first_max(&histogram);
984 let most = histogram[first];
985 if most == 0 {
986 return Err(lacks_notes());
987 }
988 histogram[first] = 0;
989 let second = histogram[first_max(&histogram)];
990 vector[0] = (second as FloatType / most as FloatType).into();
991 Ok(())
992}
993
994fn interval_between_strongest_pitches(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
995 vector[0] = match most_common(data.midi_pitch_histogram()).as_slice() {
996 [first, second, ..] => (second.0 - first.0).abs().into(),
997 _ => Value::Float(0.0),
998 };
999 Ok(())
1000}
1001
1002fn interval_between_strongest_pitch_classes(
1003 data: &DataInstance,
1004 vector: &mut [Value],
1005) -> Result<()> {
1006 let mut histogram = *data.pitch_class_histogram();
1007 let first = first_max(&histogram);
1008 histogram[first] = 0;
1009 let second = first_max(&histogram);
1010 vector[0] = first.abs_diff(second).into();
1011 Ok(())
1012}
1013
1014fn number_of_common_pitches(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1015 let histogram = data.midi_pitch_histogram();
1016 let total = total_of(histogram) as FloatType;
1017 vector[0] = histogram
1018 .iter()
1019 .filter(|(_, count)| *count as FloatType / total >= 0.09)
1020 .count()
1021 .into();
1022 Ok(())
1023}
1024
1025fn pitch_variety(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1026 vector[0] = data
1029 .midi_pitch_histogram()
1030 .iter()
1031 .filter(|(midi, _)| *midi >= 1)
1032 .count()
1033 .into();
1034 Ok(())
1035}
1036
1037fn pitch_class_variety(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1038 vector[0] = data
1039 .pitch_class_histogram()
1040 .iter()
1041 .filter(|count| **count >= 1)
1042 .count()
1043 .into();
1044 Ok(())
1045}
1046
1047fn range(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1048 let histogram = data.midi_pitch_histogram();
1049 let lowest = histogram.iter().map(|(midi, _)| *midi).min();
1050 let highest = histogram.iter().map(|(midi, _)| *midi).max();
1051 let (Some(lowest), Some(highest)) = (lowest, highest) else {
1052 return Err(lacks_notes());
1053 };
1054 vector[0] = (highest - lowest).into();
1055 Ok(())
1056}
1057
1058fn most_common_pitch(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1059 vector[0] = most_common(data.midi_pitch_histogram())
1060 .first()
1061 .map_or(Value::Float(0.0), |(midi, _)| (*midi).into());
1062 Ok(())
1063}
1064
1065fn primary_register(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1066 let pitches = data.pitches();
1067 if pitches.is_empty() {
1068 return Err(lacks_notes());
1069 }
1070 let heights: Vec<FloatType> = pitches.iter().map(|pitch| pitch.ps()).collect();
1071 vector[0] = crate::statistics::mean(&heights).into();
1072 Ok(())
1073}
1074
1075fn register_share(data: &DataInstance, within: impl Fn(IntegerType) -> bool) -> Result<FloatType> {
1077 let histogram = data.midi_pitch_histogram();
1078 if histogram.is_empty() {
1079 return Err(lacks_notes());
1080 }
1081 let matched: usize = histogram
1082 .iter()
1083 .filter(|(midi, _)| within(*midi))
1084 .map(|(_, count)| count)
1085 .sum();
1086 Ok(matched as FloatType / total_of(histogram) as FloatType)
1087}
1088
1089fn importance_of_bass_register(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1090 vector[0] = register_share(data, |midi| midi <= 54)?.into();
1091 Ok(())
1092}
1093
1094fn importance_of_middle_register(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1095 vector[0] = register_share(data, |midi| (55..=72).contains(&midi))?.into();
1096 Ok(())
1097}
1098
1099fn importance_of_high_register(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1100 vector[0] = register_share(data, |midi| midi >= 73)?.into();
1101 Ok(())
1102}
1103
1104fn most_common_pitch_class(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1105 vector[0] = first_max(data.pitch_class_histogram()).into();
1106 Ok(())
1107}
1108
1109fn basic_pitch_histogram(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1110 for (midi, count) in data.midi_pitch_histogram() {
1111 if let Some(value) = usize::try_from(*midi)
1112 .ok()
1113 .and_then(|midi| vector.get_mut(midi))
1114 {
1115 *value = (*count).into();
1116 }
1117 }
1118 Ok(())
1119}
1120
1121fn pitch_class_distribution(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1122 let histogram = data.pitch_class_histogram();
1123 let most = first_max(histogram);
1124 for (class, count) in histogram.iter().enumerate() {
1125 vector[(class + 12 - most) % 12] = (*count).into();
1126 }
1127 Ok(())
1128}
1129
1130const FIFTHS: [usize; 12] = [0, 7, 2, 9, 4, 11, 6, 1, 8, 3, 10, 5];
1132
1133fn fifths_pitch_histogram(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1134 for (class, count) in data.pitch_class_histogram().iter().enumerate() {
1135 vector[FIFTHS[class]] = (*count).into();
1136 }
1137 Ok(())
1138}
1139
1140fn quality(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1141 vector[0] = data
1142 .key_modes()
1143 .iter()
1144 .find_map(|mode| match mode.as_str() {
1145 "major" => Some(Value::Integer(0)),
1146 "minor" => Some(Value::Integer(1)),
1147 _ => None,
1148 })
1149 .unwrap_or(Value::Integer(0));
1150 Ok(())
1151}
1152
1153fn onsets(data: &DataInstance) -> Result<Vec<FloatType>> {
1155 let mut onsets: Vec<FloatType> = data.seconds_map()?.iter().map(|note| note.offset).collect();
1156 onsets.sort_by(FloatType::total_cmp);
1157 Ok(onsets)
1158}
1159
1160fn time_between(onsets: &[FloatType]) -> Vec<FloatType> {
1163 onsets
1164 .windows(2)
1165 .map(|pair| pair[1] - pair[0])
1166 .filter(|gap| gap.abs() > 1e-7)
1167 .collect()
1168}
1169
1170fn onsets_by_part(data: &DataInstance) -> Result<Vec<Vec<FloatType>>> {
1172 if data.parts_count() > 0 {
1173 data.parts()[..data.parts_count()]
1174 .iter()
1175 .map(onsets)
1176 .collect()
1177 } else {
1178 Ok(vec![onsets(data)?])
1179 }
1180}
1181
1182fn note_density(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1183 let seconds = data.seconds_map()?;
1184 let last = seconds
1185 .iter()
1186 .map(|note| note.end)
1187 .fold(None, |most: Option<FloatType>, end| {
1188 Some(most.map_or(end, |most| most.max(end)))
1189 });
1190 vector[0] = match last {
1191 Some(0.0) => return Err(Error::Feature("float division by zero".to_string())),
1192 Some(last) => (seconds.len() as FloatType / last).into(),
1193 None => Value::Float(0.0),
1194 };
1195 Ok(())
1196}
1197
1198fn average_note_duration(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1199 let seconds = data.seconds_map()?;
1200 if seconds.is_empty() {
1201 return Err(lacks_notes());
1202 }
1203 let mut total = 0.0;
1204 for note in seconds {
1205 total += note.duration;
1206 }
1207 vector[0] = (total / seconds.len() as FloatType).into();
1208 Ok(())
1209}
1210
1211fn variability_of_note_duration(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1212 let seconds = data.seconds_map()?;
1213 if seconds.is_empty() {
1214 return Err(lacks_notes());
1215 }
1216 let durations: Vec<FloatType> = seconds.iter().map(|note| note.duration).collect();
1217 vector[0] = crate::statistics::pstdev(&durations).into();
1218 Ok(())
1219}
1220
1221fn maximum_note_duration(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1222 let seconds = data.seconds_map()?;
1223 if seconds.is_empty() {
1224 return Err(lacks_notes());
1225 }
1226 let mut longest = 0.0;
1227 for note in seconds {
1228 if note.duration > longest {
1229 longest = note.duration;
1230 }
1231 }
1232 vector[0] = longest.into();
1233 Ok(())
1234}
1235
1236fn minimum_note_duration(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1237 let seconds = data.seconds_map()?;
1238 let Some(first) = seconds.first() else {
1239 return Err(lacks_notes());
1240 };
1241 let mut shortest = first.duration;
1242 for note in seconds {
1243 if note.duration < shortest {
1244 shortest = note.duration;
1245 }
1246 }
1247 vector[0] = shortest.into();
1248 Ok(())
1249}
1250
1251fn staccato_incidence(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1252 let seconds = data.seconds_map()?;
1253 if seconds.is_empty() {
1254 return Err(lacks_notes());
1255 }
1256 let short = seconds.iter().filter(|note| note.duration < 0.10).count();
1257 vector[0] = (short as FloatType / seconds.len() as FloatType).into();
1258 Ok(())
1259}
1260
1261fn average_time_between_attacks(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1262 let onsets = onsets(data)?;
1263 if onsets.is_empty() {
1264 return Err(lacks_notes());
1265 }
1266 let gaps = time_between(&onsets);
1267 if gaps.is_empty() {
1268 return Err(Error::Feature("division by zero".to_string()));
1269 }
1270 vector[0] = (crate::statistics::python_sum(&gaps) / gaps.len() as FloatType).into();
1271 Ok(())
1272}
1273
1274fn variability_of_time_between_attacks(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1275 let onsets = onsets(data)?;
1276 if onsets.is_empty() {
1277 return Err(lacks_notes());
1278 }
1279 let gaps = time_between(&onsets);
1280 if gaps.is_empty() {
1281 return Err(Error::Feature(
1282 "pstdev requires at least one data point".to_string(),
1283 ));
1284 }
1285 vector[0] = crate::statistics::pstdev(&gaps).into();
1286 Ok(())
1287}
1288
1289fn average_time_between_attacks_for_each_voice(
1290 data: &DataInstance,
1291 vector: &mut [Value],
1292) -> Result<()> {
1293 let mut averages = Vec::new();
1294 for onsets in onsets_by_part(data)? {
1295 let gaps = time_between(&onsets);
1296 if gaps.is_empty() {
1297 return Err(Error::Feature("at least one part lacks notes".to_string()));
1298 }
1299 averages.push(crate::statistics::python_sum(&gaps) / gaps.len() as FloatType);
1300 }
1301 vector[0] = (crate::statistics::python_sum(&averages) / averages.len() as FloatType).into();
1302 Ok(())
1303}
1304
1305fn average_variability_of_time_between_attacks_for_each_voice(
1306 data: &DataInstance,
1307 vector: &mut [Value],
1308) -> Result<()> {
1309 let mut deviations = Vec::new();
1310 for onsets in onsets_by_part(data)? {
1311 let gaps = time_between(&onsets);
1312 if gaps.is_empty() {
1313 return Err(Error::Feature("at least one part lacks notes".to_string()));
1314 }
1315 deviations.push(crate::statistics::pstdev(&gaps));
1316 }
1317 vector[0] = (crate::statistics::python_sum(&deviations) / deviations.len() as FloatType).into();
1318 Ok(())
1319}
1320
1321fn initial_tempo(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1322 let boundaries = data.metronome_mark_boundaries()?;
1323 vector[0] = boundaries
1324 .first()
1325 .and_then(|(_, _, mark)| mark.sounding_quarter_bpm())
1326 .ok_or_else(|| Error::Feature("the first tempo says no number".to_string()))?
1327 .into();
1328 Ok(())
1329}
1330
1331fn initial_time_signature(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1332 if let Some(meter) = data.time_signatures().first() {
1333 vector[0] = meter.numerator().into();
1334 vector[1] = meter.denominator().into();
1335 }
1336 Ok(())
1337}
1338
1339fn compound_or_simple_meter(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1340 if data
1341 .time_signatures()
1342 .first()
1343 .is_some_and(|meter| meter.beat_division_count_name() == "Compound")
1344 {
1345 vector[0] = Value::Integer(1);
1346 }
1347 Ok(())
1348}
1349
1350fn triple_meter(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1351 if data
1352 .time_signatures()
1353 .first()
1354 .is_some_and(|meter| meter.numerator() == 3)
1355 {
1356 vector[0] = Value::Integer(1);
1357 }
1358 Ok(())
1359}
1360
1361fn quintuple_meter(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1362 if data
1363 .time_signatures()
1364 .first()
1365 .is_some_and(|meter| meter.numerator() == 5)
1366 {
1367 vector[0] = Value::Integer(1);
1368 }
1369 Ok(())
1370}
1371
1372fn changes_of_meter(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1373 let meters = data.time_signatures();
1374 if let [first, rest @ ..] = meters.as_slice()
1375 && rest.iter().any(|meter| !first.ratio_equal(meter))
1376 {
1377 vector[0] = Value::Integer(1);
1378 }
1379 Ok(())
1380}
1381
1382fn duration(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1383 let seconds = data.seconds_map()?;
1384 if seconds.is_empty() {
1385 return Err(Error::Feature("input lacks duration".to_string()));
1386 }
1387 vector[0] = seconds
1388 .iter()
1389 .map(|note| note.end)
1390 .fold(FloatType::NEG_INFINITY, FloatType::max)
1391 .into();
1392 Ok(())
1393}
1394
1395fn maximum_number_of_independent_voices(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1396 vector[0] = data
1397 .parts_sounding()?
1398 .iter()
1399 .copied()
1400 .max()
1401 .unwrap_or(0)
1402 .into();
1403 Ok(())
1404}
1405
1406fn average_number_of_independent_voices(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1407 let sounding = data.parts_sounding()?;
1408 if sounding.is_empty() {
1409 return Err(lacks_notes());
1410 }
1411 vector[0] = (sounding.iter().sum::<usize>() as FloatType / sounding.len() as FloatType).into();
1412 Ok(())
1413}
1414
1415fn variability_of_number_of_independent_voices(
1416 data: &DataInstance,
1417 vector: &mut [Value],
1418) -> Result<()> {
1419 let sounding = data.parts_sounding()?;
1420 if sounding.is_empty() {
1421 return Err(lacks_notes());
1422 }
1423 let counts: Vec<FloatType> = sounding.iter().map(|count| *count as FloatType).collect();
1424 vector[0] = crate::statistics::pstdev(&counts).into();
1425 Ok(())
1426}
1427
1428fn instrument_parts(data: &DataInstance) -> Result<Vec<(Option<Option<u8>>, usize)>> {
1433 let partitioned = crate::instrument::partition_by_instrument(data.prepared());
1434 if partitioned.is_empty() {
1435 return Err(Error::Feature("input lacks instruments".to_string()));
1436 }
1437 Ok(partitioned
1438 .parts()
1439 .into_iter()
1440 .map(|part| {
1441 let program = part
1442 .events()
1443 .iter()
1444 .find_map(|event| match event.element() {
1445 StreamElement::Instrument(instrument) => Some(instrument.midi_program()),
1446 _ => None,
1447 });
1448 let notes = part
1449 .recurse()
1450 .into_iter()
1451 .filter(|(_, element)| {
1452 matches!(
1453 element,
1454 StreamElement::Note(_)
1455 | StreamElement::Chord(_)
1456 | StreamElement::Unpitched(_)
1457 | StreamElement::PercussionChord(_)
1458 | StreamElement::ChordSymbol(_)
1459 )
1460 })
1461 .count();
1462 (program, notes)
1463 })
1464 .collect())
1465}
1466
1467fn program_of(program: Option<Option<u8>>) -> Result<usize> {
1470 match program {
1471 Some(Some(program)) => Ok(usize::from(program)),
1472 Some(None) => Err(Error::Feature(
1473 "an instrument lacks a midiProgram".to_string(),
1474 )),
1475 None => Err(Error::Feature(
1476 "'NoneType' object has no attribute 'midiProgram'".to_string(),
1477 )),
1478 }
1479}
1480
1481fn pitch_total(data: &DataInstance) -> usize {
1484 data.pitch_class_histogram().iter().sum()
1485}
1486
1487fn pitched_instruments_present(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1488 for (program, notes) in instrument_parts(data)? {
1489 if notes > 0 {
1490 vector[program_of(program)?] = Value::Integer(1);
1491 }
1492 }
1493 Ok(())
1494}
1495
1496fn note_prevalence_of_pitched_instruments(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1497 let total = pitch_total(data);
1498 for (program, notes) in instrument_parts(data)? {
1499 if notes > 0 {
1500 let program = program_of(program)?;
1501 if total == 0 {
1502 return Err(Error::Feature("division by zero".to_string()));
1503 }
1504 vector[program] = (notes as FloatType / total as FloatType).into();
1505 }
1506 }
1507 Ok(())
1508}
1509
1510fn variability_of_note_prevalence_of_pitched_instruments(
1511 data: &DataInstance,
1512 vector: &mut [Value],
1513) -> Result<()> {
1514 let parts = instrument_parts(data)?;
1515 let total = pitch_total(data);
1516 if total == 0 {
1517 return Err(lacks_notes());
1518 }
1519 let shares: Vec<FloatType> = parts
1520 .iter()
1521 .filter(|(_, notes)| *notes > 0)
1522 .map(|(_, notes)| *notes as FloatType / total as FloatType)
1523 .collect();
1524 if shares.is_empty() {
1525 return Err(Error::Feature("division by zero".to_string()));
1526 }
1527 let mean = crate::statistics::python_sum(&shares) / shares.len() as FloatType;
1528 let squares: Vec<FloatType> = shares.iter().map(|share| (share - mean).powi(2)).collect();
1529 vector[0] = (crate::statistics::python_sum(&squares) / squares.len() as FloatType)
1530 .sqrt()
1531 .into();
1532 Ok(())
1533}
1534
1535fn number_of_pitched_instruments(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1536 vector[0] = instrument_parts(data)?
1537 .iter()
1538 .filter(|(_, notes)| *notes > 0)
1539 .count()
1540 .into();
1541 Ok(())
1542}
1543
1544fn instrument_fraction(data: &DataInstance, programs: &[usize]) -> Result<FloatType> {
1547 let parts = instrument_parts(data)?;
1548 let total = pitch_total(data);
1549 if total == 0 {
1550 return Err(lacks_notes());
1551 }
1552 let mut count = 0;
1553 for (program, notes) in parts {
1554 let Some(program) = program else {
1557 return Err(program_of(None).unwrap_err());
1558 };
1559 if program.is_some_and(|program| programs.contains(&usize::from(program))) {
1560 count += notes;
1561 }
1562 }
1563 Ok(count as FloatType / total as FloatType)
1564}
1565
1566fn string_keyboard_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1567 vector[0] = instrument_fraction(data, &[0, 1, 2, 3, 4, 5, 6, 7])?.into();
1568 Ok(())
1569}
1570
1571fn acoustic_guitar_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1572 vector[0] = instrument_fraction(data, &[24, 25])?.into();
1573 Ok(())
1574}
1575
1576fn electric_guitar_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1577 vector[0] = instrument_fraction(data, &[26, 27, 28, 29, 30, 31])?.into();
1578 Ok(())
1579}
1580
1581fn violin_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1582 vector[0] = instrument_fraction(data, &[40, 110])?.into();
1583 Ok(())
1584}
1585
1586fn saxophone_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1587 vector[0] = instrument_fraction(data, &[64, 65, 66, 67])?.into();
1588 Ok(())
1589}
1590
1591fn brass_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1592 vector[0] = instrument_fraction(data, &[56, 57, 58, 59, 60, 61])?.into();
1593 Ok(())
1594}
1595
1596fn woodwinds_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1597 vector[0] =
1598 instrument_fraction(data, &[68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79])?.into();
1599 Ok(())
1600}
1601
1602fn orchestral_strings_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1603 vector[0] = instrument_fraction(data, &[41, 42, 43, 44, 45])?.into();
1604 Ok(())
1605}
1606
1607fn string_ensemble_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1608 vector[0] = instrument_fraction(data, &[48, 49, 50, 51])?.into();
1609 Ok(())
1610}
1611
1612fn electric_instrument_fraction(data: &DataInstance, vector: &mut [Value]) -> Result<()> {
1613 vector[0] = instrument_fraction(
1614 data,
1615 &[
1616 4, 5, 16, 18, 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39,
1617 ],
1618 )?
1619 .into();
1620 Ok(())
1621}