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.
- partitura/directions.py +3 -0
- partitura/display.py +0 -1
- partitura/io/__init__.py +41 -35
- partitura/io/exportmatch.py +52 -10
- partitura/io/exportmidi.py +37 -19
- partitura/io/exportmusicxml.py +6 -92
- partitura/io/exportparangonada.py +18 -19
- partitura/io/importkern.py +2 -4
- partitura/io/importmatch.py +121 -39
- partitura/io/importmei.py +161 -34
- partitura/io/importmidi.py +23 -14
- partitura/io/importmusic21.py +0 -1
- partitura/io/importmusicxml.py +48 -63
- partitura/io/importparangonada.py +0 -1
- partitura/io/matchfile_base.py +0 -21
- partitura/io/matchfile_utils.py +29 -17
- partitura/io/matchlines_v0.py +0 -22
- partitura/io/matchlines_v1.py +8 -42
- partitura/io/musescore.py +68 -41
- partitura/musicanalysis/__init__.py +1 -1
- partitura/musicanalysis/note_array_to_score.py +147 -92
- partitura/musicanalysis/note_features.py +66 -51
- partitura/musicanalysis/performance_codec.py +140 -96
- partitura/musicanalysis/performance_features.py +190 -129
- partitura/musicanalysis/pitch_spelling.py +0 -2
- partitura/musicanalysis/tonal_tension.py +0 -6
- partitura/musicanalysis/voice_separation.py +1 -22
- partitura/performance.py +178 -5
- partitura/score.py +154 -74
- partitura/utils/__init__.py +1 -1
- partitura/utils/generic.py +3 -7
- partitura/utils/misc.py +0 -1
- partitura/utils/music.py +108 -66
- partitura/utils/normalize.py +75 -35
- partitura/utils/synth.py +1 -7
- {partitura-1.3.0.dist-info → partitura-1.4.0.dist-info}/METADATA +2 -2
- partitura-1.4.0.dist-info/RECORD +51 -0
- {partitura-1.3.0.dist-info → partitura-1.4.0.dist-info}/WHEEL +1 -1
- partitura-1.3.0.dist-info/RECORD +0 -51
- {partitura-1.3.0.dist-info → partitura-1.4.0.dist-info}/LICENSE +0 -0
- {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
|
|
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 =
|
|
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
|
-
|
|
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:
|