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
partitura/score.py
CHANGED
|
@@ -9,14 +9,14 @@ object). This object serves as a timeline at which musical elements
|
|
|
9
9
|
are registered in terms of their start and end times.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
from copy import copy
|
|
12
|
+
from copy import copy, deepcopy
|
|
13
13
|
from collections import defaultdict
|
|
14
14
|
from collections.abc import Iterable
|
|
15
15
|
from numbers import Number
|
|
16
16
|
|
|
17
17
|
# import copy
|
|
18
18
|
from partitura.utils.music import MUSICAL_BEATS, INTERVALCLASSES
|
|
19
|
-
import warnings
|
|
19
|
+
import warnings, sys
|
|
20
20
|
import numpy as np
|
|
21
21
|
from scipy.interpolate import PPoly
|
|
22
22
|
from typing import Union, List, Optional, Iterator, Iterable as Itertype
|
|
@@ -282,7 +282,7 @@ class Part(object):
|
|
|
282
282
|
"""
|
|
283
283
|
# operations to avoid None values and filter them efficiently.
|
|
284
284
|
m_it = self.measures
|
|
285
|
-
|
|
285
|
+
|
|
286
286
|
measures = np.array(
|
|
287
287
|
[
|
|
288
288
|
[
|
|
@@ -387,7 +387,6 @@ class Part(object):
|
|
|
387
387
|
return int_interp1d
|
|
388
388
|
|
|
389
389
|
def _time_interpolator(self, quarter=False, inv=False, musical_beat=False):
|
|
390
|
-
|
|
391
390
|
if len(self._points) < 2:
|
|
392
391
|
return lambda x: np.zeros(len(x))
|
|
393
392
|
|
|
@@ -434,13 +433,11 @@ class Part(object):
|
|
|
434
433
|
m1 = next(self.first_point.iter_starting(Measure), None)
|
|
435
434
|
|
|
436
435
|
if m1 and m1.start is not None and m1.end is not None:
|
|
437
|
-
|
|
438
436
|
f = interp1d(x, y)
|
|
439
437
|
actual_dur = np.diff(f((m1.start.t, m1.end.t)))[0]
|
|
440
438
|
ts = next(m1.start.iter_starting(TimeSignature), None)
|
|
441
439
|
|
|
442
440
|
if ts:
|
|
443
|
-
|
|
444
441
|
normal_dur = ts.beats
|
|
445
442
|
if quarter:
|
|
446
443
|
normal_dur *= 4 / ts.beat_type
|
|
@@ -618,6 +615,18 @@ class Part(object):
|
|
|
618
615
|
"""
|
|
619
616
|
return [e for e in self.iter_all(LoudnessDirection, include_subclasses=True)]
|
|
620
617
|
|
|
618
|
+
@property
|
|
619
|
+
def tempo_directions(self):
|
|
620
|
+
"""Return a list of all tempo direction in the part
|
|
621
|
+
|
|
622
|
+
Returns
|
|
623
|
+
-------
|
|
624
|
+
list
|
|
625
|
+
List of TempoDirection objects
|
|
626
|
+
|
|
627
|
+
"""
|
|
628
|
+
return [e for e in self.iter_all(TempoDirection, include_subclasses=True)]
|
|
629
|
+
|
|
621
630
|
@property
|
|
622
631
|
def articulations(self):
|
|
623
632
|
"""Return a list of all Articulation markings in the part
|
|
@@ -643,9 +652,7 @@ class Part(object):
|
|
|
643
652
|
|
|
644
653
|
"""
|
|
645
654
|
add_segments(self)
|
|
646
|
-
return [
|
|
647
|
-
e for e in self.iter_all(Segment, include_subclasses=False)
|
|
648
|
-
]
|
|
655
|
+
return [e for e in self.iter_all(Segment, include_subclasses=False)]
|
|
649
656
|
|
|
650
657
|
def quarter_durations(self, start=None, end=None):
|
|
651
658
|
"""Return an Nx2 array with quarter duration (second column)
|
|
@@ -1459,7 +1466,6 @@ class TimePoint(ComparableMixin):
|
|
|
1459
1466
|
]
|
|
1460
1467
|
|
|
1461
1468
|
if ending_items:
|
|
1462
|
-
|
|
1463
1469
|
result.append("{}".format(tree).rstrip())
|
|
1464
1470
|
|
|
1465
1471
|
if starting_items:
|
|
@@ -1472,7 +1478,6 @@ class TimePoint(ComparableMixin):
|
|
|
1472
1478
|
result.append("{}".format(tree).rstrip())
|
|
1473
1479
|
|
|
1474
1480
|
for i, item in enumerate(ending_items):
|
|
1475
|
-
|
|
1476
1481
|
if i == (len(ending_items) - 1):
|
|
1477
1482
|
tree.last_item()
|
|
1478
1483
|
else:
|
|
@@ -1483,7 +1488,6 @@ class TimePoint(ComparableMixin):
|
|
|
1483
1488
|
tree.pop()
|
|
1484
1489
|
|
|
1485
1490
|
if starting_items:
|
|
1486
|
-
|
|
1487
1491
|
result.append("{}".format(tree).rstrip())
|
|
1488
1492
|
tree.last_item()
|
|
1489
1493
|
result.append("{}starting objects".format(tree))
|
|
@@ -1491,7 +1495,6 @@ class TimePoint(ComparableMixin):
|
|
|
1491
1495
|
result.append("{}".format(tree).rstrip())
|
|
1492
1496
|
|
|
1493
1497
|
for i, item in enumerate(starting_items):
|
|
1494
|
-
|
|
1495
1498
|
if i == (len(starting_items) - 1):
|
|
1496
1499
|
tree.last_item()
|
|
1497
1500
|
else:
|
|
@@ -2160,7 +2163,6 @@ class Clef(TimedObject):
|
|
|
2160
2163
|
"""
|
|
2161
2164
|
|
|
2162
2165
|
def __init__(self, staff, sign, line, octave_change):
|
|
2163
|
-
|
|
2164
2166
|
super().__init__()
|
|
2165
2167
|
self.staff = staff
|
|
2166
2168
|
self.sign = sign
|
|
@@ -2572,7 +2574,6 @@ class Staff(TimedObject):
|
|
|
2572
2574
|
return f"{super().__str__()} number={self.number} lines={self.lines}"
|
|
2573
2575
|
|
|
2574
2576
|
|
|
2575
|
-
|
|
2576
2577
|
class KeySignature(TimedObject):
|
|
2577
2578
|
"""Key signature.
|
|
2578
2579
|
|
|
@@ -2677,7 +2678,6 @@ class Words(TimedObject):
|
|
|
2677
2678
|
return f'{super().__str__()} "{self.text}"'
|
|
2678
2679
|
|
|
2679
2680
|
|
|
2680
|
-
|
|
2681
2681
|
class OctaveShiftDirection(TimedObject):
|
|
2682
2682
|
"""An octave shift direction.
|
|
2683
2683
|
|
|
@@ -2685,6 +2685,7 @@ class OctaveShiftDirection(TimedObject):
|
|
|
2685
2685
|
----------
|
|
2686
2686
|
|
|
2687
2687
|
"""
|
|
2688
|
+
|
|
2688
2689
|
def __init__(self, shift_type, shift_size=8, staff=None):
|
|
2689
2690
|
super().__init__()
|
|
2690
2691
|
self.shift_type = shift_type
|
|
@@ -2699,16 +2700,16 @@ class OctaveShiftDirection(TimedObject):
|
|
|
2699
2700
|
class Harmony(TimedObject):
|
|
2700
2701
|
"""A harmony element in the score not currently used.
|
|
2701
2702
|
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2703
|
+
Parameters
|
|
2704
|
+
----------
|
|
2705
|
+
text : str
|
|
2706
|
+
The harmony text
|
|
2706
2707
|
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2708
|
+
Attributes
|
|
2709
|
+
----------
|
|
2710
|
+
text : str
|
|
2711
|
+
See parameters
|
|
2712
|
+
"""
|
|
2712
2713
|
|
|
2713
2714
|
def __init__(self, text):
|
|
2714
2715
|
super().__init__()
|
|
@@ -2744,6 +2745,7 @@ class RomanNumeral(TimedObject):
|
|
|
2744
2745
|
|
|
2745
2746
|
class ChordSymbol(TimedObject):
|
|
2746
2747
|
"""A harmony element in the score usually for Chord Symbols."""
|
|
2748
|
+
|
|
2747
2749
|
def __init__(self, root, kind, bass=None):
|
|
2748
2750
|
super().__init__()
|
|
2749
2751
|
self.kind = kind
|
|
@@ -2767,6 +2769,7 @@ class Interval(object):
|
|
|
2767
2769
|
direction : str
|
|
2768
2770
|
The interval direction (e.g. up, down)
|
|
2769
2771
|
"""
|
|
2772
|
+
|
|
2770
2773
|
def __init__(self, number, quality, direction="up"):
|
|
2771
2774
|
self.number = number
|
|
2772
2775
|
self.quality = quality
|
|
@@ -2776,8 +2779,13 @@ class Interval(object):
|
|
|
2776
2779
|
def validate(self):
|
|
2777
2780
|
number = self.number % 7
|
|
2778
2781
|
number = 7 if number == 0 else number
|
|
2779
|
-
assert
|
|
2780
|
-
|
|
2782
|
+
assert (
|
|
2783
|
+
self.quality + str(number) in INTERVALCLASSES
|
|
2784
|
+
), f"Interval {number}{self.quality} not found"
|
|
2785
|
+
assert self.direction in [
|
|
2786
|
+
"up",
|
|
2787
|
+
"down",
|
|
2788
|
+
], f"Interval direction {self.direction} not found"
|
|
2781
2789
|
|
|
2782
2790
|
def __str__(self):
|
|
2783
2791
|
return f'{super().__str__()} "{self.number}{self.quality}"'
|
|
@@ -2994,8 +3002,16 @@ class Score(object):
|
|
|
2994
3002
|
the identifier should not start with a number.
|
|
2995
3003
|
partlist : `Part`, `PartGroup` or list of `Part` or `PartGroup` instances.
|
|
2996
3004
|
List of `Part` or `PartGroup` objects.
|
|
2997
|
-
|
|
2998
|
-
|
|
3005
|
+
work_title: str, optional
|
|
3006
|
+
Work title of the score, if applicable.
|
|
3007
|
+
work_number: str, optional
|
|
3008
|
+
Work number of the score, if applicable.
|
|
3009
|
+
movement_title: str, optional
|
|
3010
|
+
Movement title of the score, if applicable.
|
|
3011
|
+
movement_number: str, optional
|
|
3012
|
+
Movement number of the score, if applicable.
|
|
3013
|
+
title : str, optional
|
|
3014
|
+
Title of the score, from <credit-words> tag
|
|
2999
3015
|
subtitle: str, optional
|
|
3000
3016
|
Subtitle of the score.
|
|
3001
3017
|
composer: str, optional
|
|
@@ -3014,7 +3030,13 @@ class Score(object):
|
|
|
3014
3030
|
part_structure: list of `Part` or `PartGrop`
|
|
3015
3031
|
List of all `Part` or `PartGroup` objects that specify the structure of
|
|
3016
3032
|
the score.
|
|
3017
|
-
|
|
3033
|
+
work_title: str
|
|
3034
|
+
See parameters.
|
|
3035
|
+
work_number: str
|
|
3036
|
+
See parameters.
|
|
3037
|
+
movement_title: str
|
|
3038
|
+
See parameters.
|
|
3039
|
+
movement_number: str
|
|
3018
3040
|
See parameters.
|
|
3019
3041
|
subtitle: str
|
|
3020
3042
|
See parameters.
|
|
@@ -3028,6 +3050,10 @@ class Score(object):
|
|
|
3028
3050
|
"""
|
|
3029
3051
|
|
|
3030
3052
|
id: Optional[str]
|
|
3053
|
+
work_title: Optional[str]
|
|
3054
|
+
work_number: Optional[str]
|
|
3055
|
+
movement_title: Optional[str]
|
|
3056
|
+
movement_number: Optional[str]
|
|
3031
3057
|
title: Optional[str]
|
|
3032
3058
|
subtitle: Optional[str]
|
|
3033
3059
|
composer: Optional[str]
|
|
@@ -3040,6 +3066,10 @@ class Score(object):
|
|
|
3040
3066
|
self,
|
|
3041
3067
|
partlist: Union[Part, PartGroup, Itertype[Union[Part, PartGroup]]],
|
|
3042
3068
|
id: Optional[str] = None,
|
|
3069
|
+
work_title: Optional[str] = None,
|
|
3070
|
+
work_number: Optional[str] = None,
|
|
3071
|
+
movement_title: Optional[str] = None,
|
|
3072
|
+
movement_number: Optional[str] = None,
|
|
3043
3073
|
title: Optional[str] = None,
|
|
3044
3074
|
subtitle: Optional[str] = None,
|
|
3045
3075
|
composer: Optional[str] = None,
|
|
@@ -3049,6 +3079,10 @@ class Score(object):
|
|
|
3049
3079
|
self.id = id
|
|
3050
3080
|
|
|
3051
3081
|
# Score Information (default from MuseScore/MusicXML)
|
|
3082
|
+
self.work_title = work_title
|
|
3083
|
+
self.work_number = work_number
|
|
3084
|
+
self.movement_title = movement_title
|
|
3085
|
+
self.movement_number = movement_number
|
|
3052
3086
|
self.title = title
|
|
3053
3087
|
self.subtitle = subtitle
|
|
3054
3088
|
self.composer = composer
|
|
@@ -3181,7 +3215,6 @@ class ScoreVariant(object):
|
|
|
3181
3215
|
tp_new = part.get_or_add_point(tp.t + delta)
|
|
3182
3216
|
o_gen = (o for oo in tp.starting_objects.values() for o in oo)
|
|
3183
3217
|
for o in o_gen:
|
|
3184
|
-
|
|
3185
3218
|
# special cases:
|
|
3186
3219
|
|
|
3187
3220
|
# don't include some TimedObjects in the unfolded part
|
|
@@ -3326,7 +3359,6 @@ def add_measures(part):
|
|
|
3326
3359
|
pos = ts_start
|
|
3327
3360
|
|
|
3328
3361
|
while pos < ts_end:
|
|
3329
|
-
|
|
3330
3362
|
measure_start = pos
|
|
3331
3363
|
measure_end_beats = min(beat_map(pos) + measure_dur, beat_map(end))
|
|
3332
3364
|
measure_end = min(ts_end, inv_beat_map(measure_end_beats))
|
|
@@ -3348,7 +3380,11 @@ def add_measures(part):
|
|
|
3348
3380
|
else:
|
|
3349
3381
|
measure_end = existing_measure.start.t
|
|
3350
3382
|
|
|
3351
|
-
part.add(
|
|
3383
|
+
part.add(
|
|
3384
|
+
Measure(number=mcounter, name=str(mcounter)),
|
|
3385
|
+
int(measure_start),
|
|
3386
|
+
int(measure_end),
|
|
3387
|
+
)
|
|
3352
3388
|
|
|
3353
3389
|
# if measure exists but was not at measure_start,
|
|
3354
3390
|
# a filler measure is added with number mcounter
|
|
@@ -3586,7 +3622,6 @@ def tie_notes(part):
|
|
|
3586
3622
|
succeeded = 0
|
|
3587
3623
|
for i, note in enumerate(list(part.iter_all(Note))):
|
|
3588
3624
|
if note.symbolic_duration is None:
|
|
3589
|
-
|
|
3590
3625
|
splits = find_tie_split(
|
|
3591
3626
|
note.start.t, note.end.t, int(divs_map(note.start.t)), max_splits
|
|
3592
3627
|
)
|
|
@@ -3626,29 +3661,22 @@ def _set_end_times(part, cls):
|
|
|
3626
3661
|
t = None
|
|
3627
3662
|
|
|
3628
3663
|
for obj in part.iter_all(cls, include_subclasses=True):
|
|
3629
|
-
|
|
3630
3664
|
if obj.start == t:
|
|
3631
|
-
|
|
3632
3665
|
if obj.end is None:
|
|
3633
|
-
|
|
3634
3666
|
acc.append(obj)
|
|
3635
3667
|
|
|
3636
3668
|
else:
|
|
3637
|
-
|
|
3638
3669
|
for o in acc:
|
|
3639
|
-
|
|
3640
3670
|
part.add(o, end=obj.start.t)
|
|
3641
3671
|
|
|
3642
3672
|
acc = []
|
|
3643
3673
|
|
|
3644
3674
|
if obj.end is None:
|
|
3645
|
-
|
|
3646
3675
|
acc.append(obj)
|
|
3647
3676
|
|
|
3648
3677
|
t = obj.start
|
|
3649
3678
|
|
|
3650
3679
|
for o in acc:
|
|
3651
|
-
|
|
3652
3680
|
part.add(o, end=part.last_point.t)
|
|
3653
3681
|
|
|
3654
3682
|
|
|
@@ -3728,7 +3756,6 @@ def find_tuplets(part):
|
|
|
3728
3756
|
|
|
3729
3757
|
# 1. group consecutive notes without symbolic_duration
|
|
3730
3758
|
for note in part.iter_all(GenericNote, include_subclasses=True):
|
|
3731
|
-
|
|
3732
3759
|
if note.symbolic_duration is None:
|
|
3733
3760
|
if note.start.t == prev_end:
|
|
3734
3761
|
candidates[-1].append(note)
|
|
@@ -3738,10 +3765,8 @@ def find_tuplets(part):
|
|
|
3738
3765
|
|
|
3739
3766
|
# 2. within each group
|
|
3740
3767
|
for group in candidates:
|
|
3741
|
-
|
|
3742
3768
|
# 3. search for the predefined list of tuplets
|
|
3743
3769
|
for actual_notes in search_for_tuplets:
|
|
3744
|
-
|
|
3745
3770
|
if actual_notes > len(group):
|
|
3746
3771
|
# tuplet requires more notes than we have
|
|
3747
3772
|
continue
|
|
@@ -3759,7 +3784,6 @@ def find_tuplets(part):
|
|
|
3759
3784
|
# continue
|
|
3760
3785
|
tup_start += 1
|
|
3761
3786
|
else:
|
|
3762
|
-
|
|
3763
3787
|
start = note_tuplet[0].start.t
|
|
3764
3788
|
end = note_tuplet[-1].end.t
|
|
3765
3789
|
total_dur = end - start
|
|
@@ -3813,7 +3837,6 @@ def sanitize_part(part, tie_tolerance=0):
|
|
|
3813
3837
|
for no in part.iter_all(
|
|
3814
3838
|
Note, include_subclasses=False, start=gn.start.t, end=gn.start.t + 1
|
|
3815
3839
|
):
|
|
3816
|
-
|
|
3817
3840
|
if no.voice == gn.voice:
|
|
3818
3841
|
gn.last_grace_note_in_seq.grace_next = no
|
|
3819
3842
|
|
|
@@ -4004,7 +4027,6 @@ def add_segments(part):
|
|
|
4004
4027
|
|
|
4005
4028
|
# loop through the boundaries at the end of current segment
|
|
4006
4029
|
for boundary_type in boundaries[se].keys():
|
|
4007
|
-
|
|
4008
4030
|
# REPEATS
|
|
4009
4031
|
if boundary_type == "repeat_start":
|
|
4010
4032
|
segment_info[ss]["to"].append(segment_info[se]["ID"])
|
|
@@ -4073,7 +4095,7 @@ def add_segments(part):
|
|
|
4073
4095
|
)
|
|
4074
4096
|
|
|
4075
4097
|
# NAVIGATION SYMBOLS
|
|
4076
|
-
|
|
4098
|
+
|
|
4077
4099
|
# Navigation1_ = destinations that should only be used after all others
|
|
4078
4100
|
# Navigation2_ = destinations that are used *after* a jump
|
|
4079
4101
|
if boundary_type == "coda":
|
|
@@ -4103,10 +4125,10 @@ def add_segments(part):
|
|
|
4103
4125
|
# find the segno and jump there
|
|
4104
4126
|
segno_time = destinations["segno"][0]
|
|
4105
4127
|
segment_info[ss]["to"].append(
|
|
4106
|
-
"Navigation1_" + segment_info[segno_time]["ID"]
|
|
4128
|
+
"Navigation1_" + segment_info[segno_time]["ID"]
|
|
4107
4129
|
)
|
|
4108
4130
|
segment_info[ss]["to"].append(
|
|
4109
|
-
"Navigation2_" + segment_info[se]["ID"]
|
|
4131
|
+
"Navigation2_" + segment_info[se]["ID"]
|
|
4110
4132
|
)
|
|
4111
4133
|
segment_info[ss]["type"] = "leap_start"
|
|
4112
4134
|
segment_info[ss]["info"].append("dal segno")
|
|
@@ -4118,7 +4140,7 @@ def add_segments(part):
|
|
|
4118
4140
|
"Navigation1_" + segment_info[part.first_point.t]["ID"]
|
|
4119
4141
|
)
|
|
4120
4142
|
segment_info[ss]["to"].append(
|
|
4121
|
-
"Navigation2_" + segment_info[se]["ID"]
|
|
4143
|
+
"Navigation2_" + segment_info[se]["ID"]
|
|
4122
4144
|
)
|
|
4123
4145
|
segment_info[ss]["type"] = "leap_start"
|
|
4124
4146
|
segment_info[ss]["info"].append("da capo")
|
|
@@ -4240,8 +4262,9 @@ def pretty_segments(part):
|
|
|
4240
4262
|
+ str(part.beat_map(segments[p].end.t))
|
|
4241
4263
|
)
|
|
4242
4264
|
+ "\t duration: "
|
|
4243
|
-
+ "{:<6}".format(
|
|
4244
|
-
|
|
4265
|
+
+ "{:<6}".format(
|
|
4266
|
+
str(part.beat_map(segments[p].end.t) - part.beat_map(segments[p].start.t))
|
|
4267
|
+
)
|
|
4245
4268
|
+ "\t info: "
|
|
4246
4269
|
+ str(segments[p].info)
|
|
4247
4270
|
for p in segments.keys()
|
|
@@ -4279,7 +4302,6 @@ class Path:
|
|
|
4279
4302
|
all_repeats=False,
|
|
4280
4303
|
jumped=False,
|
|
4281
4304
|
):
|
|
4282
|
-
|
|
4283
4305
|
self.path = path_list
|
|
4284
4306
|
self.segments = segments
|
|
4285
4307
|
if used_segment_jumps is None:
|
|
@@ -4353,7 +4375,7 @@ class Path:
|
|
|
4353
4375
|
for key in self.used_segment_jumps:
|
|
4354
4376
|
for used_dest in self.used_segment_jumps[key]:
|
|
4355
4377
|
new_path.used_segment_jumps[key].append(used_dest)
|
|
4356
|
-
|
|
4378
|
+
|
|
4357
4379
|
return new_path
|
|
4358
4380
|
|
|
4359
4381
|
def make_copy_with_jump_to(self, destination, ignore_leap_info=True):
|
|
@@ -4366,7 +4388,7 @@ class Path:
|
|
|
4366
4388
|
new_path = self.copy()
|
|
4367
4389
|
new_path.used_segment_jumps[new_path.path[-1]].append(destination)
|
|
4368
4390
|
new_path.path.append(destination)
|
|
4369
|
-
|
|
4391
|
+
|
|
4370
4392
|
if (
|
|
4371
4393
|
new_path.segments[destination].type == "leap_end"
|
|
4372
4394
|
and new_path.segments[new_path.path[-2]].type == "leap_start"
|
|
@@ -4386,25 +4408,25 @@ class Path:
|
|
|
4386
4408
|
# delete used destinations
|
|
4387
4409
|
new_path.used_segment_jumps[segid] = list()
|
|
4388
4410
|
# add the jump destination to the used ones
|
|
4389
|
-
new_path.used_segment_jumps[new_path.path[-2]].append(destination)
|
|
4390
|
-
|
|
4411
|
+
new_path.used_segment_jumps[new_path.path[-2]].append(destination)
|
|
4412
|
+
|
|
4391
4413
|
if not ignore_leap_info:
|
|
4392
4414
|
new_path.no_repeats = True
|
|
4393
4415
|
return new_path
|
|
4394
4416
|
|
|
4395
4417
|
@property
|
|
4396
|
-
def list_of_destinations_from_last_segment(self):
|
|
4418
|
+
def list_of_destinations_from_last_segment(self):
|
|
4397
4419
|
destinations = list(self.segments[self.path[-1]].to)
|
|
4398
4420
|
previously_used_destinations = self.used_segment_jumps[self.path[-1]]
|
|
4399
4421
|
# only continue in order of the sequence, after full consumption, start at zero
|
|
4400
4422
|
# if the full or minimal sequence is forced,
|
|
4401
4423
|
# return only the single possible jump destination, else return possibly many.
|
|
4402
|
-
|
|
4403
|
-
if len(previously_used_destinations) != 0:
|
|
4424
|
+
|
|
4425
|
+
if len(previously_used_destinations) != 0:
|
|
4404
4426
|
last_destination = previously_used_destinations[-1]
|
|
4405
4427
|
last_destination_count = previously_used_destinations.count(
|
|
4406
4428
|
last_destination
|
|
4407
|
-
)
|
|
4429
|
+
)
|
|
4408
4430
|
last_destination_index = [
|
|
4409
4431
|
i for i, n in enumerate(destinations * 100) if n == last_destination
|
|
4410
4432
|
][last_destination_count - 1]
|
|
@@ -4604,15 +4626,15 @@ def iter_unfolded_parts(part, update_ids=True):
|
|
|
4604
4626
|
|
|
4605
4627
|
|
|
4606
4628
|
# UPDATED VERSION
|
|
4607
|
-
def unfold_part_maximal(
|
|
4608
|
-
"""Return the "maximally" unfolded part, that is, a copy of the
|
|
4629
|
+
def unfold_part_maximal(score: ScoreLike, update_ids=True, ignore_leaps=True):
|
|
4630
|
+
"""Return the "maximally" unfolded part/score, that is, a copy of the
|
|
4609
4631
|
part where all segments marked with repeat signs are included
|
|
4610
4632
|
twice.
|
|
4611
4633
|
|
|
4612
4634
|
Parameters
|
|
4613
4635
|
----------
|
|
4614
|
-
|
|
4615
|
-
The Part to unfold.
|
|
4636
|
+
score : ScoreLike
|
|
4637
|
+
The Part/Score to unfold.
|
|
4616
4638
|
update_ids : bool (optional)
|
|
4617
4639
|
Update note ids to reflect the repetitions. Note IDs will have
|
|
4618
4640
|
a '-<repetition number>', e.g., 'n132-1' and 'n132-2'
|
|
@@ -4625,19 +4647,75 @@ def unfold_part_maximal(part, update_ids=True, ignore_leaps=True):
|
|
|
4625
4647
|
|
|
4626
4648
|
Returns
|
|
4627
4649
|
-------
|
|
4628
|
-
unfolded_part :
|
|
4629
|
-
The unfolded Part
|
|
4650
|
+
unfolded_part : ScoreLike
|
|
4651
|
+
The unfolded Part/Score
|
|
4630
4652
|
|
|
4631
4653
|
"""
|
|
4654
|
+
if isinstance(score, Score):
|
|
4655
|
+
# Copy needs to be deep, otherwise the recursion limit will be exceeded
|
|
4656
|
+
old_recursion_depth = sys.getrecursionlimit()
|
|
4657
|
+
sys.setrecursionlimit(10000)
|
|
4658
|
+
# Deep copy of score
|
|
4659
|
+
new_score = deepcopy(score)
|
|
4660
|
+
# Reset recursion limit to previous value to avoid side effects
|
|
4661
|
+
sys.setrecursionlimit(old_recursion_depth)
|
|
4662
|
+
new_partlist = list()
|
|
4663
|
+
for score in new_score.parts:
|
|
4664
|
+
unfolded_part = unfold_part_maximal(
|
|
4665
|
+
score, update_ids=update_ids, ignore_leaps=ignore_leaps
|
|
4666
|
+
)
|
|
4667
|
+
new_partlist.append(unfolded_part)
|
|
4668
|
+
new_score.parts = new_partlist
|
|
4669
|
+
return new_score
|
|
4632
4670
|
|
|
4633
4671
|
paths = get_paths(
|
|
4634
|
-
|
|
4672
|
+
score, no_repeats=False, all_repeats=True, ignore_leap_info=ignore_leaps
|
|
4635
4673
|
)
|
|
4636
4674
|
|
|
4637
|
-
unfolded_part = new_part_from_path(paths[0],
|
|
4675
|
+
unfolded_part = new_part_from_path(paths[0], score, update_ids=update_ids)
|
|
4638
4676
|
return unfolded_part
|
|
4639
4677
|
|
|
4640
4678
|
|
|
4679
|
+
def unfold_part_minimal(score: ScoreLike):
|
|
4680
|
+
"""Return the "minimally" unfolded score/part, that is, a copy of the
|
|
4681
|
+
part where all segments marked with repeat are included only once.
|
|
4682
|
+
For voltas only the last volta segment is included.
|
|
4683
|
+
Note this might not be musically valid, e.g. a passing a "fine" even a first time will stop this unfolding.
|
|
4684
|
+
Warning: The unfolding of repeats is computed part-wise, inconsistent repeat markings of parts of a single result
|
|
4685
|
+
in inconsistent unfoldings.
|
|
4686
|
+
|
|
4687
|
+
Parameters
|
|
4688
|
+
----------
|
|
4689
|
+
score: ScoreLike
|
|
4690
|
+
The score/part to unfold.
|
|
4691
|
+
|
|
4692
|
+
Returns
|
|
4693
|
+
-------
|
|
4694
|
+
unfolded_score : ScoreLike
|
|
4695
|
+
The unfolded Part
|
|
4696
|
+
|
|
4697
|
+
"""
|
|
4698
|
+
if isinstance(score, Score):
|
|
4699
|
+
# Copy needs to be deep, otherwise the recursion limit will be exceeded
|
|
4700
|
+
old_recursion_depth = sys.getrecursionlimit()
|
|
4701
|
+
sys.setrecursionlimit(10000)
|
|
4702
|
+
# Deep copy of score
|
|
4703
|
+
unfolded_score = deepcopy(score)
|
|
4704
|
+
# Reset recursion limit to previous value to avoid side effects
|
|
4705
|
+
sys.setrecursionlimit(old_recursion_depth)
|
|
4706
|
+
new_partlist = list()
|
|
4707
|
+
for part in unfolded_score.parts:
|
|
4708
|
+
unfolded_part = unfold_part_minimal(part)
|
|
4709
|
+
new_partlist.append(unfolded_part)
|
|
4710
|
+
unfolded_score.parts = new_partlist
|
|
4711
|
+
return unfolded_score
|
|
4712
|
+
|
|
4713
|
+
paths = get_paths(score, no_repeats=True, all_repeats=False, ignore_leap_info=True)
|
|
4714
|
+
|
|
4715
|
+
unfolded_score = new_part_from_path(paths[0], score, update_ids=False)
|
|
4716
|
+
return unfolded_score
|
|
4717
|
+
|
|
4718
|
+
|
|
4641
4719
|
# UPDATED / UNCHANGED VERSION
|
|
4642
4720
|
def unfold_part_alignment(part, alignment):
|
|
4643
4721
|
"""Return the unfolded part given an alignment, that is, a copy
|
|
@@ -4858,13 +4936,15 @@ def merge_parts(parts, reassign="voice"):
|
|
|
4858
4936
|
e.voice = e.voice + sum(maximum_voices[:p_ind])
|
|
4859
4937
|
elif reassign == "staff":
|
|
4860
4938
|
if isinstance(e, (GenericNote, Words, Direction)):
|
|
4861
|
-
|
|
4862
|
-
|
|
4939
|
+
e.staff = (e.staff if e.staff is not None else 1) + sum(
|
|
4940
|
+
maximum_staves[:p_ind]
|
|
4941
|
+
)
|
|
4863
4942
|
elif isinstance(
|
|
4864
4943
|
e, Clef
|
|
4865
4944
|
): # TODO: to update if "number" get changed in "staff"
|
|
4866
|
-
|
|
4867
|
-
|
|
4945
|
+
e.staff = (e.staff if e.staff is not None else 1) + sum(
|
|
4946
|
+
maximum_staves[:p_ind]
|
|
4947
|
+
)
|
|
4868
4948
|
new_part.add(e, start=new_start, end=new_end)
|
|
4869
4949
|
|
|
4870
4950
|
# new_part.add(copy.deepcopy(e), start=new_start, end=new_end)
|
partitura/utils/__init__.py
CHANGED
partitura/utils/generic.py
CHANGED
|
@@ -114,18 +114,15 @@ def iter_current_next(iterable, start=_sentinel, end=_sentinel):
|
|
|
114
114
|
|
|
115
115
|
cur = start
|
|
116
116
|
try:
|
|
117
|
-
|
|
118
117
|
if cur is _sentinel:
|
|
119
118
|
cur = next(iterable)
|
|
120
119
|
|
|
121
120
|
while True:
|
|
122
|
-
|
|
123
121
|
nxt = next(iterable)
|
|
124
122
|
yield (cur, nxt)
|
|
125
123
|
cur = nxt
|
|
126
124
|
|
|
127
125
|
except StopIteration:
|
|
128
|
-
|
|
129
126
|
if cur is not _sentinel and end is not _sentinel:
|
|
130
127
|
yield (cur, end)
|
|
131
128
|
|
|
@@ -570,14 +567,12 @@ def interp1d(
|
|
|
570
567
|
)
|
|
571
568
|
|
|
572
569
|
else:
|
|
573
|
-
|
|
574
570
|
# If there is only one value for x and y, assume that the method
|
|
575
571
|
# will always return the same value for any input.
|
|
576
572
|
|
|
577
573
|
def interp_fun(
|
|
578
574
|
input_var: Union[float, int, np.ndarray]
|
|
579
575
|
) -> Callable[[Union[float, int, np.ndarray]], np.ndarray]:
|
|
580
|
-
|
|
581
576
|
if y.ndim > 1:
|
|
582
577
|
result = np.broadcast_to(y, (len(np.atleast_1d(input_var)), y.shape[1]))
|
|
583
578
|
else:
|
|
@@ -635,7 +630,7 @@ def monotonize_times(
|
|
|
635
630
|
s_mono: np.ndarray
|
|
636
631
|
a monotonic sequence that has been linearly interpolated using a subset of s
|
|
637
632
|
x_mono: np.ndarray
|
|
638
|
-
The input of the monotonic sequence.
|
|
633
|
+
The input of the monotonic sequence.
|
|
639
634
|
"""
|
|
640
635
|
eps = np.finfo(float).eps
|
|
641
636
|
|
|
@@ -649,9 +644,10 @@ def monotonize_times(
|
|
|
649
644
|
mask = np.r_[False, True, (np.diff(s_mono) != 0), False]
|
|
650
645
|
x_mono = _x[1:-1]
|
|
651
646
|
s_mono = interp1d(_x[mask], _s[mask], fill_value="extrapolate")(x_mono)
|
|
652
|
-
|
|
647
|
+
|
|
653
648
|
return s_mono, x_mono
|
|
654
649
|
|
|
650
|
+
|
|
655
651
|
if __name__ == "__main__":
|
|
656
652
|
import doctest
|
|
657
653
|
|