partitura 1.3.0__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. partitura/directions.py +3 -0
  2. partitura/display.py +0 -1
  3. partitura/io/__init__.py +41 -35
  4. partitura/io/exportmatch.py +52 -10
  5. partitura/io/exportmidi.py +37 -19
  6. partitura/io/exportmusicxml.py +6 -92
  7. partitura/io/exportparangonada.py +18 -19
  8. partitura/io/importkern.py +2 -4
  9. partitura/io/importmatch.py +121 -39
  10. partitura/io/importmei.py +161 -34
  11. partitura/io/importmidi.py +23 -14
  12. partitura/io/importmusic21.py +0 -1
  13. partitura/io/importmusicxml.py +48 -63
  14. partitura/io/importparangonada.py +0 -1
  15. partitura/io/matchfile_base.py +0 -21
  16. partitura/io/matchfile_utils.py +29 -17
  17. partitura/io/matchlines_v0.py +0 -22
  18. partitura/io/matchlines_v1.py +8 -42
  19. partitura/io/musescore.py +68 -41
  20. partitura/musicanalysis/__init__.py +1 -1
  21. partitura/musicanalysis/note_array_to_score.py +147 -92
  22. partitura/musicanalysis/note_features.py +66 -51
  23. partitura/musicanalysis/performance_codec.py +140 -96
  24. partitura/musicanalysis/performance_features.py +190 -129
  25. partitura/musicanalysis/pitch_spelling.py +0 -2
  26. partitura/musicanalysis/tonal_tension.py +0 -6
  27. partitura/musicanalysis/voice_separation.py +1 -22
  28. partitura/performance.py +178 -5
  29. partitura/score.py +154 -74
  30. partitura/utils/__init__.py +1 -1
  31. partitura/utils/generic.py +3 -7
  32. partitura/utils/misc.py +0 -1
  33. partitura/utils/music.py +108 -66
  34. partitura/utils/normalize.py +75 -35
  35. partitura/utils/synth.py +1 -7
  36. {partitura-1.3.0.dist-info → partitura-1.4.0.dist-info}/METADATA +2 -2
  37. partitura-1.4.0.dist-info/RECORD +51 -0
  38. {partitura-1.3.0.dist-info → partitura-1.4.0.dist-info}/WHEEL +1 -1
  39. partitura-1.3.0.dist-info/RECORD +0 -51
  40. {partitura-1.3.0.dist-info → partitura-1.4.0.dist-info}/LICENSE +0 -0
  41. {partitura-1.3.0.dist-info → partitura-1.4.0.dist-info}/top_level.txt +0 -0
@@ -124,16 +124,14 @@ def estimate_voices(note_info, monophonic_voices=True):
124
124
  # grace_by_key[i].append(
125
125
 
126
126
  if monophonic_voices:
127
-
128
127
  # identity mapping
129
128
  idx_equivs = dict((n, n) for n in notearray["id"])
130
129
  input_array = notearray
131
130
 
132
131
  else:
133
-
134
132
  note_by_key = defaultdict(list)
135
133
 
136
- for (pitch, onset, dur, i) in notearray:
134
+ for pitch, onset, dur, i in notearray:
137
135
  note_by_key[(onset, dur)].append(i)
138
136
 
139
137
  # dict that maps first chord note index to the list of all note indices
@@ -270,7 +268,6 @@ def est_best_connections(cost, mode="prev"):
270
268
 
271
269
  # while there are fewer than n_assignments
272
270
  while len(best_assignment) < n_assignments:
273
-
274
271
  # Get the remaining minimal cost
275
272
  next_best = mcost.min(1).argmin()
276
273
  next_assig = mcost.argmin(1)[next_best]
@@ -367,7 +364,6 @@ class VSNote(object):
367
364
  """
368
365
 
369
366
  def __init__(self, pitch, onset, duration, note_id, velocity=None, voice=None):
370
-
371
367
  # ID of the note
372
368
  self.id = note_id
373
369
  self.pitch = pitch
@@ -423,7 +419,6 @@ class VSChord(object):
423
419
  """
424
420
 
425
421
  def __init__(self, notes, rep_note="highest"):
426
-
427
422
  if any([n.onset != notes[0].onset for n in notes]):
428
423
  raise ValueError("All notes in the chord must have the same onset")
429
424
  if any([n.offset != notes[0].offset for n in notes]):
@@ -470,7 +465,6 @@ class Voice(object):
470
465
  """
471
466
 
472
467
  def __init__(self, stream_or_streams, voice=None):
473
-
474
468
  if isinstance(stream_or_streams, list):
475
469
  self.streams = stream_or_streams
476
470
  elif isinstance(stream_or_streams, NoteStream):
@@ -485,7 +479,6 @@ class Voice(object):
485
479
  self.notes = []
486
480
 
487
481
  def _setup_voice(self):
488
-
489
482
  # sort stream by onset
490
483
  self.streams.sort(key=lambda x: x.onset)
491
484
 
@@ -549,7 +542,6 @@ class VoiceManager(object):
549
542
  """
550
543
 
551
544
  def __init__(self, num_voices):
552
-
553
545
  self.num_voices = num_voices
554
546
 
555
547
  self.voices = [Voice([], voice) for voice in range(self.num_voices)]
@@ -578,7 +570,6 @@ class VSBaseScore(object):
578
570
  """
579
571
 
580
572
  def __init__(self, notes):
581
-
582
573
  # Set list of notes
583
574
  self.notes = notes
584
575
 
@@ -679,7 +670,6 @@ class VSBaseScore(object):
679
670
 
680
671
  class NoteStream(VSBaseScore):
681
672
  def __init__(self, notes=[], voice="auto", prev_stream=None, next_stream=None):
682
-
683
673
  super(NoteStream, self).__init__(notes)
684
674
  self._voice = voice
685
675
  self.prev_stream = prev_stream
@@ -738,7 +728,6 @@ class NoteStream(VSBaseScore):
738
728
 
739
729
  class Contig(VSBaseScore):
740
730
  def __init__(self, notes, is_maxcontig=False):
741
-
742
731
  super(Contig, self).__init__(notes)
743
732
  self._is_maxcontig = is_maxcontig
744
733
  # Onset time of the contig
@@ -758,7 +747,6 @@ class Contig(VSBaseScore):
758
747
 
759
748
  for on in self.unique_onsets[np.where(self.unique_onsets >= self.onset)[0]]:
760
749
  for i, note in enumerate(self.sounding_notes(on)):
761
-
762
750
  if note not in streams[i]:
763
751
  streams[i].append(note)
764
752
 
@@ -808,7 +796,6 @@ class VoSA(VSBaseScore):
808
796
  """
809
797
 
810
798
  def __init__(self, score, delete_gracenotes=False):
811
-
812
799
  # Score
813
800
  self.score = score
814
801
 
@@ -847,7 +834,6 @@ class VoSA(VSBaseScore):
847
834
  self.notes = []
848
835
 
849
836
  for n in self.score:
850
-
851
837
  note = VSNote(
852
838
  pitch=n["pitch"],
853
839
  onset=n["onset"],
@@ -910,7 +896,6 @@ class VoSA(VSBaseScore):
910
896
  )
911
897
 
912
898
  def make_contigs(self):
913
-
914
899
  # number of voices at each time point in the score
915
900
  n_voices = np.array([len(sn) for sn in self])
916
901
 
@@ -935,7 +920,6 @@ class VoSA(VSBaseScore):
935
920
 
936
921
  # Look for voice status changes
937
922
  for i, sn in enumerate(self):
938
-
939
923
  # an array of booleans that indicate whether each currently sounding note
940
924
  # was sounding in the previous time point (segment)
941
925
  note_sounding_in_prev_segment = np.array([n in self[i - 1] for n in sn])
@@ -944,13 +928,11 @@ class VoSA(VSBaseScore):
944
928
  # * there are sounding notes in the previous segment; and
945
929
  # * there is a change in number of voices in the current segment
946
930
  if any(note_sounding_in_prev_segment) and n_voice_changes[i] != 0:
947
-
948
931
  # indices of the sounding notes belonging to the previous time point
949
932
  prev_sounding_note_idxs = np.where(note_sounding_in_prev_segment)[0]
950
933
 
951
934
  # Update segment boundaries
952
935
  for psnix in prev_sounding_note_idxs:
953
-
954
936
  # get sounding note
955
937
  prev_sounding_note = sn[psnix]
956
938
  # Get index of the timepoint of the onset of the prev_sounding_note
@@ -968,7 +950,6 @@ class VoSA(VSBaseScore):
968
950
 
969
951
  # iterate over timepoints, sounding notes and segment boundaries
970
952
  for tp, sn, sb, nv in zip(self.utp, self, segment_boundaries, n_voices):
971
-
972
953
  # If there is a segment boundary and there are sounding notes
973
954
  # (i.e. do not make empty contigs)
974
955
  if sb and len(sn) > 0:
@@ -1026,7 +1007,6 @@ class VoSA(VSBaseScore):
1026
1007
  while keep_loop:
1027
1008
  # Cristalization process around the maximal contigs
1028
1009
  for mci in maximal_contigs_idxs:
1029
-
1030
1010
  # Get voice manager corresponding to the current
1031
1011
  # maximal contig
1032
1012
  vm = voice_managers_dict[mci]
@@ -1063,7 +1043,6 @@ class VoSA(VSBaseScore):
1063
1043
  # been assigned (assigne voice wrt the closest
1064
1044
  # maximal contig)
1065
1045
  if not next_contig.has_voice_info:
1066
-
1067
1046
  # flag voices without a connection in
1068
1047
  # the previous step in the loop
1069
1048
  for es in f_unassigned:
partitura/performance.py CHANGED
@@ -85,7 +85,11 @@ class PerformedPart(object):
85
85
  super().__init__()
86
86
  self.id = id
87
87
  self.part_name = part_name
88
- self.notes = notes
88
+ self.notes = list(
89
+ map(
90
+ lambda n: n if isinstance(n, PerformedNote) else PerformedNote(n), notes
91
+ )
92
+ )
89
93
  self.controls = controls or []
90
94
  self.programs = programs or []
91
95
  self.ppq = ppq
@@ -203,7 +207,11 @@ class PerformedPart(object):
203
207
  if "id" not in note_array.dtype.names:
204
208
  n_ids = ["n{0}".format(i) for i in range(len(note_array))]
205
209
  else:
206
- n_ids = note_array["id"]
210
+ # Check if all ids are the same
211
+ if np.all(note_array["id"] == note_array["id"][0]):
212
+ n_ids = ["n{0}".format(i) for i in range(len(note_array))]
213
+ else:
214
+ n_ids = note_array["id"]
207
215
 
208
216
  if "track" not in note_array.dtype.names:
209
217
  tracks = np.zeros(len(note_array), dtype=int)
@@ -280,6 +288,174 @@ def adjust_offsets_w_sustain(
280
288
  note["sound_off"] = offset
281
289
 
282
290
 
291
+ class PerformedNote:
292
+ """
293
+ A dictionary-like object representing a performed note.
294
+
295
+ Parameters
296
+ ----------
297
+ pnote_dict : dict
298
+ A dictionary containing performed note information.
299
+ This information can contain the following fields:
300
+ "id", "pitch", "note_on", "note_off", "velocity", "track", "channel", "sound_off".
301
+ If not provided, the default values will be used.
302
+ Pitch, note_on, and note_off are required.
303
+ """
304
+
305
+ def __init__(self, pnote_dict):
306
+ self.pnote_dict = pnote_dict
307
+ self.pnote_dict["id"] = self.pnote_dict.get("id", None)
308
+ self.pnote_dict["pitch"] = self.pnote_dict.get("pitch", self["midi_pitch"])
309
+ self.pnote_dict["note_on"] = self.pnote_dict.get("note_on", -1)
310
+ self.pnote_dict["note_off"] = self.pnote_dict.get("note_off", -1)
311
+ self.pnote_dict["sound_off"] = self.pnote_dict.get(
312
+ "sound_off", self["note_off"]
313
+ )
314
+ self.pnote_dict["track"] = self.pnote_dict.get("track", 0)
315
+ self.pnote_dict["channel"] = self.pnote_dict.get("channel", 1)
316
+ self.pnote_dict["velocity"] = self.pnote_dict.get("velocity", 60)
317
+ self._validate_values(pnote_dict)
318
+ self._accepted_keys = [
319
+ "id",
320
+ "pitch",
321
+ "note_on",
322
+ "note_off",
323
+ "velocity",
324
+ "track",
325
+ "channel",
326
+ "sound_off",
327
+ "note_on_tick",
328
+ "note_off_tick",
329
+ ]
330
+
331
+ def __str__(self):
332
+ return f"PerformedNote: {self['id']}"
333
+
334
+ def __eq__(self, other):
335
+ if not isinstance(PerformedNote):
336
+ return False
337
+ if not self.keys() == other.keys():
338
+ return False
339
+ return np.all(
340
+ np.array([self[k] == other[k] for k in self.keys() if k in other.keys()])
341
+ )
342
+
343
+ def keys(self):
344
+ return self.pnote_dict.keys()
345
+
346
+ def get(self, key, default=None):
347
+ return self.pnote_dict.get(key, default)
348
+
349
+ def __hash__(self):
350
+ return hash(self["id"])
351
+
352
+ def __lt__(self, other):
353
+ return self["note_on"] < other["note_on"]
354
+
355
+ def __le__(self, other):
356
+ return self["note_on"] <= other["note_on"]
357
+
358
+ def __gt__(self, other):
359
+ return self["note_on"] > other["note_on"]
360
+
361
+ def __ge__(self, other):
362
+ return self["note_on"] >= other["note_on"]
363
+
364
+ def __getitem__(self, key):
365
+ return self.pnote_dict.get(key, None)
366
+
367
+ def __setitem__(self, key, value):
368
+ if key not in self._accepted_keys:
369
+ raise KeyError(f"Key {key} not accepted for PerformedNote")
370
+ self._validate_values((key, value))
371
+ self.pnote_dict[key] = value
372
+
373
+ def __delitem__(self, key):
374
+ raise KeyError("Cannot delete items from PerformedNote")
375
+
376
+ def __iter__(self):
377
+ return iter(self.keys())
378
+
379
+ def __len__(self):
380
+ return len(self.keys())
381
+
382
+ def __contains__(self, key):
383
+ return key in self.keys()
384
+
385
+ def copy(self):
386
+ return PerformedNote(self.pnote_dict.copy())
387
+
388
+ def _validate_values(self, d):
389
+ if isinstance(d, dict):
390
+ dd = d
391
+ elif isinstance(d, tuple):
392
+ dd = {d[0]: d[1]}
393
+ else:
394
+ raise ValueError(f"Invalid value {d} provided for PerformedNote")
395
+
396
+ for key, value in dd.items():
397
+ if key == "pitch":
398
+ self._validate_pitch(value)
399
+ elif key == "note_on":
400
+ self._validate_note_on(value)
401
+ elif key == "note_off":
402
+ self._validate_note_off(value)
403
+ elif key == "velocity":
404
+ self._validate_velocity(value)
405
+ elif key == "sound_off":
406
+ self._validate_sound_off(value)
407
+ elif key == "note_on_tick":
408
+ self._validate_note_on_tick(value)
409
+ elif key == "note_off_tick":
410
+ self._validate_note_off_tick(value)
411
+
412
+ def _validate_sound_off(self, value):
413
+ if self.get("note_off", -1) < 0:
414
+ return
415
+ if value < 0:
416
+ raise ValueError(f"sound_off must be greater than or equal to 0")
417
+ if value < self.pnote_dict["note_off"]:
418
+ raise ValueError(f"sound_off must be greater or equal to note_off")
419
+
420
+ def _validate_note_on(self, value):
421
+ if value < 0:
422
+ raise ValueError(
423
+ f"Note on value provided is invalid, must be greater than or equal to 0"
424
+ )
425
+
426
+ def _validate_note_off(self, value):
427
+ if self.pnote_dict.get("note_on", -1) < 0:
428
+ return
429
+ if value < 0 or value < self.pnote_dict["note_on"]:
430
+ raise ValueError(
431
+ f"Note off value provided is invalid, "
432
+ f"must be a positive value greater than or equal to 0 and greater or equal to note_on"
433
+ )
434
+
435
+ def _validate_note_on_tick(self, value):
436
+ if value < 0:
437
+ raise ValueError(
438
+ f"Note on tick value provided is invalid, must be greater than or equal to 0"
439
+ )
440
+
441
+ def _validate_note_off_tick(self, value):
442
+ if self.pnote_dict.get("note_on_tick", -1) < 0:
443
+ return
444
+ if value < 0 or value < self.pnote_dict["note_on_tick"]:
445
+ raise ValueError(
446
+ f"Note off tick value provided is invalid, "
447
+ f"must be a positive value greater than or equal to 0 and greater or equal to note_on_tick"
448
+ )
449
+
450
+ def _validate_pitch(self, value):
451
+ if value > 127 or value < 0:
452
+ raise ValueError(f"pitch must be between 0 and 127")
453
+
454
+ def _validate_velocity(self, value):
455
+ if value > 127 or value < 0:
456
+ raise ValueError(f"velocity must be between 0 and 127")
457
+
458
+
283
459
  class Performance(object):
284
460
  """Main object for representing a performance.
285
461
 
@@ -345,7 +521,6 @@ class Performance(object):
345
521
  if isinstance(performedparts, PerformedPart):
346
522
  self.performedparts = [performedparts]
347
523
  elif isinstance(performedparts, Itertype):
348
-
349
524
  if not all([isinstance(pp, PerformedPart) for pp in performedparts]):
350
525
  raise ValueError(
351
526
  "`performedparts` should be a list of `PerformedPart` objects!"
@@ -416,9 +591,7 @@ class Performance(object):
416
591
  track_map = dict([(tid, ti) for ti, tid in enumerate(unique_track_ids)])
417
592
 
418
593
  for i, ppart in enumerate(self):
419
-
420
594
  for note in ppart.notes:
421
-
422
595
  note["track"] = track_map[(i, note.get("track", -1))]
423
596
 
424
597
  for control in ppart.controls: