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/io/importmidi.py
CHANGED
|
@@ -124,7 +124,6 @@ def load_performance_midi(
|
|
|
124
124
|
else:
|
|
125
125
|
tracks = [(i, u) for i, u in enumerate(mid.tracks)]
|
|
126
126
|
for i, track in tracks:
|
|
127
|
-
|
|
128
127
|
notes = []
|
|
129
128
|
controls = []
|
|
130
129
|
programs = []
|
|
@@ -135,18 +134,15 @@ def load_performance_midi(
|
|
|
135
134
|
sounding_notes = {}
|
|
136
135
|
|
|
137
136
|
for msg in track:
|
|
138
|
-
|
|
139
137
|
# update time deltas when they arrive
|
|
140
138
|
t = t + msg.time * time_conversion_factor
|
|
141
139
|
ttick = ttick + msg.time
|
|
142
140
|
|
|
143
141
|
if msg.type == "set_tempo":
|
|
144
|
-
|
|
145
142
|
mpq = msg.tempo
|
|
146
143
|
time_conversion_factor = mpq / (ppq * 10**6)
|
|
147
144
|
|
|
148
145
|
elif msg.type == "control_change":
|
|
149
|
-
|
|
150
146
|
controls.append(
|
|
151
147
|
dict(
|
|
152
148
|
time=t,
|
|
@@ -159,7 +155,6 @@ def load_performance_midi(
|
|
|
159
155
|
)
|
|
160
156
|
|
|
161
157
|
elif msg.type == "program_change":
|
|
162
|
-
|
|
163
158
|
programs.append(
|
|
164
159
|
dict(
|
|
165
160
|
time=t,
|
|
@@ -171,7 +166,6 @@ def load_performance_midi(
|
|
|
171
166
|
)
|
|
172
167
|
|
|
173
168
|
else:
|
|
174
|
-
|
|
175
169
|
note_on = msg.type == "note_on"
|
|
176
170
|
note_off = msg.type == "note_off"
|
|
177
171
|
|
|
@@ -183,19 +177,16 @@ def load_performance_midi(
|
|
|
183
177
|
|
|
184
178
|
# start note if it's a 'note on' event with velocity > 0
|
|
185
179
|
if note_on and msg.velocity > 0:
|
|
186
|
-
|
|
187
180
|
# save the onset time and velocity
|
|
188
181
|
sounding_notes[note] = (t, ttick, msg.velocity)
|
|
189
182
|
|
|
190
183
|
# end note if it's a 'note off' event or 'note on' with velocity 0
|
|
191
184
|
elif note_off or (note_on and msg.velocity == 0):
|
|
192
|
-
|
|
193
185
|
if note not in sounding_notes:
|
|
194
|
-
warnings.warn("ignoring MIDI message
|
|
186
|
+
warnings.warn(f"ignoring MIDI message {msg}")
|
|
195
187
|
continue
|
|
196
188
|
|
|
197
189
|
# append the note to the list associated with the channel
|
|
198
|
-
|
|
199
190
|
notes.append(
|
|
200
191
|
dict(
|
|
201
192
|
# id=f"n{len(notes)}",
|
|
@@ -364,7 +355,6 @@ or a list of these
|
|
|
364
355
|
t_raw = 0
|
|
365
356
|
|
|
366
357
|
for msg in track:
|
|
367
|
-
|
|
368
358
|
t_raw = t_raw + msg.time
|
|
369
359
|
|
|
370
360
|
if msg.type not in relevant:
|
|
@@ -393,13 +383,11 @@ or a list of these
|
|
|
393
383
|
|
|
394
384
|
# start note if it's a 'note on' event with velocity > 0
|
|
395
385
|
if note_on and msg.velocity > 0:
|
|
396
|
-
|
|
397
386
|
# save the onset time and velocity
|
|
398
387
|
sounding_notes[note] = (t, msg.velocity)
|
|
399
388
|
|
|
400
389
|
# end note if it's a 'note off' event or 'note on' with velocity 0
|
|
401
390
|
elif note_off or (note_on and msg.velocity == 0):
|
|
402
|
-
|
|
403
391
|
if note not in sounding_notes:
|
|
404
392
|
warnings.warn("ignoring MIDI message %s" % msg)
|
|
405
393
|
continue
|
|
@@ -428,7 +416,6 @@ or a list of these
|
|
|
428
416
|
if len(ch_notes) > 0:
|
|
429
417
|
notes_by_track_ch[(track_nr, ch)] = ch_notes
|
|
430
418
|
|
|
431
|
-
|
|
432
419
|
tr_ch_keys = sorted(notes_by_track_ch.keys())
|
|
433
420
|
group_part_voice_keys, part_names, group_names = assign_group_part_voice(
|
|
434
421
|
part_voice_assign_mode, tr_ch_keys, track_names_by_track
|
|
@@ -485,6 +472,28 @@ or a list of these
|
|
|
485
472
|
else:
|
|
486
473
|
note_ids = [None for i in range(len(note_array))]
|
|
487
474
|
|
|
475
|
+
## sanitize time signature, when they are only present in one track, and no global is set
|
|
476
|
+
# find the number of ts per each track
|
|
477
|
+
number_of_time_sig_per_track = [
|
|
478
|
+
len(time_sigs_by_track[t]) for t in key_sigs_by_track.keys()
|
|
479
|
+
]
|
|
480
|
+
# if one track has 0 ts, and another has !=0 ts, and no global_time_sigs is present, sanitize
|
|
481
|
+
# all key signatures are copied to global, and the track ts are removed
|
|
482
|
+
if (
|
|
483
|
+
len(global_time_sigs) == 0
|
|
484
|
+
and min(number_of_time_sig_per_track) == 0
|
|
485
|
+
and max(number_of_time_sig_per_track) != 0
|
|
486
|
+
):
|
|
487
|
+
warnings.warn(
|
|
488
|
+
"Sanitizing time signatures. They will be shared across all tracks."
|
|
489
|
+
)
|
|
490
|
+
for ts in [
|
|
491
|
+
ts for ts_track in time_sigs_by_track.values() for ts in ts_track
|
|
492
|
+
]: # flattening all track time signatures to a list of ts
|
|
493
|
+
global_time_sigs.append(ts)
|
|
494
|
+
# now clear all track_ts
|
|
495
|
+
time_sigs_by_track.clear()
|
|
496
|
+
|
|
488
497
|
time_sigs_by_part = defaultdict(set)
|
|
489
498
|
for tr, ts_list in time_sigs_by_track.items():
|
|
490
499
|
for ts in ts_list:
|
partitura/io/importmusic21.py
CHANGED
partitura/io/importmusicxml.py
CHANGED
|
@@ -60,6 +60,22 @@ PEDAL_DIRECTIONS = {
|
|
|
60
60
|
"sustain_pedal": score.SustainPedalDirection,
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
TEMPO_DIRECTIONS = {
|
|
64
|
+
"Adagio": score.ConstantTempoDirection,
|
|
65
|
+
"Andante": score.ConstantTempoDirection,
|
|
66
|
+
"Andante amoroso": score.ConstantTempoDirection,
|
|
67
|
+
"Andante cantabile": score.ConstantTempoDirection,
|
|
68
|
+
"Andante grazioso": score.ConstantTempoDirection,
|
|
69
|
+
"Menuetto": score.ConstantTempoDirection,
|
|
70
|
+
"Allegretto grazioso": score.ConstantTempoDirection,
|
|
71
|
+
"Allegro moderato": score.ConstantTempoDirection,
|
|
72
|
+
"Allegro assai": score.ConstantTempoDirection,
|
|
73
|
+
"Allegro": score.ConstantTempoDirection,
|
|
74
|
+
"Allegretto": score.ConstantTempoDirection,
|
|
75
|
+
"Molto allegro": score.ConstantTempoDirection,
|
|
76
|
+
"Presto": score.ConstantTempoDirection,
|
|
77
|
+
}
|
|
78
|
+
|
|
63
79
|
OCTAVE_SHIFTS = {8: 1, 15: 2, 22: 3}
|
|
64
80
|
|
|
65
81
|
|
|
@@ -114,11 +130,9 @@ def _parse_partlist(partlist):
|
|
|
114
130
|
structure = []
|
|
115
131
|
current_group = None
|
|
116
132
|
part_dict = {}
|
|
117
|
-
|
|
118
133
|
for e in partlist:
|
|
119
134
|
if e.tag == "part-group":
|
|
120
135
|
if e.get("type") == "start":
|
|
121
|
-
|
|
122
136
|
gr_name = get_value_from_tag(e, "group-name", str)
|
|
123
137
|
gr_symbol = get_value_from_tag(e, "group-symbol", str)
|
|
124
138
|
gr_number = get_value_from_attribute(e, "number", int)
|
|
@@ -147,7 +161,6 @@ def _parse_partlist(partlist):
|
|
|
147
161
|
part.part_abbreviation = next(
|
|
148
162
|
iter(e.xpath("part-abbreviation/text()")), None
|
|
149
163
|
)
|
|
150
|
-
|
|
151
164
|
part_dict[part_id] = part
|
|
152
165
|
|
|
153
166
|
if current_group is None:
|
|
@@ -240,6 +253,10 @@ def load_musicxml(
|
|
|
240
253
|
|
|
241
254
|
composer = None
|
|
242
255
|
scid = None
|
|
256
|
+
work_title = None
|
|
257
|
+
work_number = None
|
|
258
|
+
movement_title = None
|
|
259
|
+
movement_number = None
|
|
243
260
|
title = None
|
|
244
261
|
subtitle = None
|
|
245
262
|
lyricist = None
|
|
@@ -255,14 +272,25 @@ def load_musicxml(
|
|
|
255
272
|
tag="work-title",
|
|
256
273
|
as_type=str,
|
|
257
274
|
)
|
|
275
|
+
scidn = get_value_from_tag(
|
|
276
|
+
e=work_info_el,
|
|
277
|
+
tag="work-number",
|
|
278
|
+
as_type=str,
|
|
279
|
+
)
|
|
280
|
+
work_title = scid
|
|
281
|
+
work_number = scidn
|
|
258
282
|
|
|
259
|
-
|
|
283
|
+
movement_title_el = document.find(".//movement-title")
|
|
284
|
+
movement_number_el = document.find(".//movement-number")
|
|
285
|
+
if movement_title_el is not None:
|
|
286
|
+
movement_title = movement_title_el.text
|
|
287
|
+
if movement_number_el is not None:
|
|
288
|
+
movement_number = movement_number_el.text
|
|
260
289
|
|
|
261
290
|
score_identification_el = document.find("identification")
|
|
262
291
|
|
|
263
292
|
# The identification tag has preference over credit
|
|
264
293
|
if score_identification_el is not None:
|
|
265
|
-
|
|
266
294
|
copyright = get_value_from_tag(score_identification_el, "rights", str)
|
|
267
295
|
|
|
268
296
|
for cel in score_identification_el.findall("creator"):
|
|
@@ -272,7 +300,6 @@ def load_musicxml(
|
|
|
272
300
|
lyricist = str(cel.text)
|
|
273
301
|
|
|
274
302
|
for cel in document.findall("credit"):
|
|
275
|
-
|
|
276
303
|
credit_type = get_value_from_tag(cel, "credit-type", str)
|
|
277
304
|
if credit_type == "title" and title is None:
|
|
278
305
|
title = get_value_from_tag(cel, "credit-words", str)
|
|
@@ -292,6 +319,10 @@ def load_musicxml(
|
|
|
292
319
|
scr = score.Score(
|
|
293
320
|
id=scid,
|
|
294
321
|
partlist=partlist,
|
|
322
|
+
work_number=work_number,
|
|
323
|
+
work_title=work_title,
|
|
324
|
+
movement_number=movement_number,
|
|
325
|
+
movement_title=movement_title,
|
|
295
326
|
title=title,
|
|
296
327
|
subtitle=subtitle,
|
|
297
328
|
composer=composer,
|
|
@@ -317,7 +348,6 @@ def _parse_parts(document, part_dict):
|
|
|
317
348
|
"""
|
|
318
349
|
|
|
319
350
|
for part_el in document.findall("part"):
|
|
320
|
-
|
|
321
351
|
part_id = part_el.get("id", "P1")
|
|
322
352
|
part = part_dict.get(part_id, score.Part(part_id))
|
|
323
353
|
|
|
@@ -415,7 +445,6 @@ def _parse_parts(document, part_dict):
|
|
|
415
445
|
# check whether all grace notes have a main note
|
|
416
446
|
for gn in part.iter_all(score.GraceNote):
|
|
417
447
|
if gn.main_note is None:
|
|
418
|
-
|
|
419
448
|
for no in part.iter_all(
|
|
420
449
|
score.Note,
|
|
421
450
|
include_subclasses=False,
|
|
@@ -456,8 +485,8 @@ def _parse_parts(document, part_dict):
|
|
|
456
485
|
|
|
457
486
|
|
|
458
487
|
def _handle_measure(measure_el, position, part, ongoing, doc_order, measure_counter):
|
|
459
|
-
"""
|
|
460
|
-
|
|
488
|
+
"""Parse a <measure>...</measure> element, adding it and its contents to the part.
|
|
489
|
+
|
|
461
490
|
Parameters
|
|
462
491
|
----------
|
|
463
492
|
measure_el : etree.Element
|
|
@@ -472,16 +501,16 @@ def _handle_measure(measure_el, position, part, ongoing, doc_order, measure_coun
|
|
|
472
501
|
The index of the first note element in the current measure in the xml file.
|
|
473
502
|
measure_counter : int
|
|
474
503
|
The index of the <measure> tag in the xml file, starting from 1
|
|
475
|
-
|
|
504
|
+
|
|
476
505
|
Returns
|
|
477
506
|
-------
|
|
478
507
|
measure_maxtime : int
|
|
479
508
|
The ending time of the measure
|
|
480
509
|
doc_order : int
|
|
481
510
|
The index of the first note element in the next measure in the xml file.
|
|
482
|
-
|
|
511
|
+
|
|
483
512
|
"""
|
|
484
|
-
|
|
513
|
+
|
|
485
514
|
# make a measure object
|
|
486
515
|
measure = make_measure(measure_el, measure_counter)
|
|
487
516
|
|
|
@@ -497,7 +526,6 @@ def _handle_measure(measure_el, position, part, ongoing, doc_order, measure_coun
|
|
|
497
526
|
measure_maxtime = measure_start
|
|
498
527
|
trailing_children = []
|
|
499
528
|
for i, e in enumerate(measure_el):
|
|
500
|
-
|
|
501
529
|
if e.tag == "backup":
|
|
502
530
|
# <xs:documentation>The backup and forward elements are required
|
|
503
531
|
# to coordinate multiple voices in one part, including music on
|
|
@@ -608,7 +636,7 @@ def _handle_measure(measure_el, position, part, ongoing, doc_order, measure_coun
|
|
|
608
636
|
|
|
609
637
|
# add end time of measure
|
|
610
638
|
part.add(measure, None, measure_maxtime)
|
|
611
|
-
|
|
639
|
+
|
|
612
640
|
return measure_maxtime, doc_order
|
|
613
641
|
|
|
614
642
|
|
|
@@ -635,13 +663,11 @@ def _handle_repeat(e, position, part, ongoing):
|
|
|
635
663
|
key = "repeat"
|
|
636
664
|
|
|
637
665
|
if e.get("direction") == "forward":
|
|
638
|
-
|
|
639
666
|
o = score.Repeat()
|
|
640
667
|
ongoing[key] = o
|
|
641
668
|
part.add(o, position)
|
|
642
669
|
|
|
643
670
|
elif e.get("direction") == "backward":
|
|
644
|
-
|
|
645
671
|
o = ongoing.pop(key, None)
|
|
646
672
|
|
|
647
673
|
if o is None:
|
|
@@ -660,17 +686,14 @@ def _handle_ending(e, position, part, ongoing):
|
|
|
660
686
|
key = ("ending", getattr(e, "number", "0"))
|
|
661
687
|
|
|
662
688
|
if e.get("type") == "start":
|
|
663
|
-
|
|
664
689
|
o = score.Ending(e.get("number"))
|
|
665
690
|
ongoing[key] = o
|
|
666
691
|
part.add(o, position)
|
|
667
692
|
|
|
668
693
|
elif e.get("type") in ("stop", "discontinue"):
|
|
669
|
-
|
|
670
694
|
o = ongoing.pop(key, None)
|
|
671
695
|
|
|
672
696
|
if o is None:
|
|
673
|
-
|
|
674
697
|
warnings.warn(
|
|
675
698
|
"Found ending[stop] without a preceding ending[start]\n"
|
|
676
699
|
+ "Single measure bracket is assumed"
|
|
@@ -679,7 +702,6 @@ def _handle_ending(e, position, part, ongoing):
|
|
|
679
702
|
part.add(o, None, position)
|
|
680
703
|
|
|
681
704
|
else:
|
|
682
|
-
|
|
683
705
|
part.add(o, None, position)
|
|
684
706
|
|
|
685
707
|
|
|
@@ -701,7 +723,6 @@ def _handle_new_page(position, part, ongoing):
|
|
|
701
723
|
|
|
702
724
|
def _handle_new_system(position, part, ongoing):
|
|
703
725
|
if "system" in ongoing:
|
|
704
|
-
|
|
705
726
|
if position == 0:
|
|
706
727
|
# ignore non-informative new-system at start of score
|
|
707
728
|
return
|
|
@@ -725,13 +746,13 @@ def make_measure(xml_measure, measure_counter):
|
|
|
725
746
|
An etree Element instance with a <measure> tag
|
|
726
747
|
measure_counter : int
|
|
727
748
|
The index of the <measure> tag in the xml file, starting from 1
|
|
728
|
-
|
|
749
|
+
|
|
729
750
|
Returns
|
|
730
751
|
-------
|
|
731
752
|
measure : score.Measure
|
|
732
753
|
A measure object with a number and optional name attribute
|
|
733
754
|
"""
|
|
734
|
-
|
|
755
|
+
|
|
735
756
|
measure = score.Measure()
|
|
736
757
|
|
|
737
758
|
measure.number = measure_counter
|
|
@@ -772,17 +793,14 @@ def get_offset(e):
|
|
|
772
793
|
offset = e.find("offset")
|
|
773
794
|
|
|
774
795
|
if offset is None:
|
|
775
|
-
|
|
776
796
|
return None
|
|
777
797
|
|
|
778
798
|
else:
|
|
779
|
-
|
|
780
799
|
sounding = offset.attrib.get("sound", "no")
|
|
781
800
|
return int(offset.text) if sounding == "yes" else 0
|
|
782
801
|
|
|
783
802
|
|
|
784
803
|
def _handle_direction(e, position, part, ongoing):
|
|
785
|
-
|
|
786
804
|
# <!--
|
|
787
805
|
# A direction is a musical indication that is not attached
|
|
788
806
|
# to a specific note. Two or more may be combined to
|
|
@@ -857,8 +875,9 @@ def _handle_direction(e, position, part, ongoing):
|
|
|
857
875
|
# first child of direction-type is dynamics, there may be subsequent
|
|
858
876
|
# dynamics items, so we loop:
|
|
859
877
|
for child in direction_type:
|
|
878
|
+
# check if child has no children, in which case continue
|
|
860
879
|
# interpret as score.Direction, fall back to score.Words
|
|
861
|
-
dyn_el = next(iter(child))
|
|
880
|
+
dyn_el = next(iter(child), None)
|
|
862
881
|
if dyn_el is not None:
|
|
863
882
|
direction = DYN_DIRECTIONS.get(dyn_el.tag, score.Words)(
|
|
864
883
|
dyn_el.tag, staff=staff
|
|
@@ -869,13 +888,11 @@ def _handle_direction(e, position, part, ongoing):
|
|
|
869
888
|
# first child of direction-type is words, there may be subsequent
|
|
870
889
|
# words items, so we loop:
|
|
871
890
|
for child in direction_type:
|
|
872
|
-
|
|
873
891
|
# try to make a direction out of words
|
|
874
892
|
parse_result = parse_direction(child.text)
|
|
875
893
|
starting_directions.extend(parse_result)
|
|
876
894
|
|
|
877
895
|
elif dt.tag == "wedge":
|
|
878
|
-
|
|
879
896
|
number = get_value_from_attribute(dt, "number", int) or 1
|
|
880
897
|
key = ("wedge", number)
|
|
881
898
|
wedge_type = get_value_from_attribute(dt, "type", str)
|
|
@@ -889,7 +906,6 @@ def _handle_direction(e, position, part, ongoing):
|
|
|
889
906
|
ongoing[key] = o
|
|
890
907
|
|
|
891
908
|
elif wedge_type == "stop":
|
|
892
|
-
|
|
893
909
|
o = ongoing.get(key)
|
|
894
910
|
if o is not None:
|
|
895
911
|
ending_directions.append(o)
|
|
@@ -898,7 +914,6 @@ def _handle_direction(e, position, part, ongoing):
|
|
|
898
914
|
warnings.warn("Did not find a wedge start element for wedge stop!")
|
|
899
915
|
|
|
900
916
|
elif dt.tag == "dashes":
|
|
901
|
-
|
|
902
917
|
# start/stop/continue
|
|
903
918
|
dashes_type = get_value_from_attribute(dt, "type", str)
|
|
904
919
|
number = get_value_from_attribute(dt, "number", int) or 1
|
|
@@ -929,7 +944,6 @@ def _handle_direction(e, position, part, ongoing):
|
|
|
929
944
|
ongoing[key] = o
|
|
930
945
|
|
|
931
946
|
elif octave_shift_type == "stop":
|
|
932
|
-
|
|
933
947
|
o = ongoing.get(key)
|
|
934
948
|
if o is not None:
|
|
935
949
|
ending_directions.append(o)
|
|
@@ -941,7 +955,6 @@ def _handle_direction(e, position, part, ongoing):
|
|
|
941
955
|
)
|
|
942
956
|
|
|
943
957
|
elif dt.tag == "pedal":
|
|
944
|
-
|
|
945
958
|
# start/stop
|
|
946
959
|
pedal_type = get_value_from_attribute(dt, "type", str)
|
|
947
960
|
pedal_line = get_value_from_attribute(dt, "line", str) == "yes"
|
|
@@ -960,7 +973,6 @@ def _handle_direction(e, position, part, ongoing):
|
|
|
960
973
|
ongoing[key] = o
|
|
961
974
|
|
|
962
975
|
elif pedal_type == "stop":
|
|
963
|
-
|
|
964
976
|
o = ongoing.get(key)
|
|
965
977
|
if o is not None:
|
|
966
978
|
ending_directions.append(o)
|
|
@@ -980,13 +992,10 @@ def _handle_direction(e, position, part, ongoing):
|
|
|
980
992
|
warnings.warn("ignoring direction type: {} {}".format(dt.tag, dt.attrib))
|
|
981
993
|
|
|
982
994
|
for dashes_key, dashes_type in dashes_keys.items():
|
|
983
|
-
|
|
984
995
|
if dashes_type == "start":
|
|
985
|
-
|
|
986
996
|
ongoing[dashes_key] = starting_directions
|
|
987
997
|
|
|
988
998
|
elif dashes_type == "stop":
|
|
989
|
-
|
|
990
999
|
oo = ongoing.get(dashes_key)
|
|
991
1000
|
if oo is None:
|
|
992
1001
|
warnings.warn("Dashes end without dashes start")
|
|
@@ -1163,11 +1172,10 @@ def _handle_sound(e, position, part):
|
|
|
1163
1172
|
if "tempo" in e.attrib:
|
|
1164
1173
|
tempo = score.Tempo(int(e.attrib["tempo"]), "q")
|
|
1165
1174
|
# part.add_starting_object(position, tempo)
|
|
1166
|
-
|
|
1175
|
+
(position, part, tempo)
|
|
1167
1176
|
|
|
1168
1177
|
|
|
1169
1178
|
def _handle_note(e, position, part, ongoing, prev_note, doc_order):
|
|
1170
|
-
|
|
1171
1179
|
# get some common features of element if available
|
|
1172
1180
|
duration = get_value_from_tag(e, "duration", int) or 0
|
|
1173
1181
|
# elements may have an explicit temporal offset
|
|
@@ -1221,7 +1229,6 @@ def _handle_note(e, position, part, ongoing, prev_note, doc_order):
|
|
|
1221
1229
|
pitch = e.find("pitch")
|
|
1222
1230
|
unpitch = e.find("unpitched")
|
|
1223
1231
|
if pitch is not None:
|
|
1224
|
-
|
|
1225
1232
|
step = get_value_from_tag(pitch, "step", str)
|
|
1226
1233
|
alter = get_value_from_tag(pitch, "alter", int)
|
|
1227
1234
|
octave = get_value_from_tag(pitch, "octave", int)
|
|
@@ -1304,30 +1311,24 @@ def _handle_note(e, position, part, ongoing, prev_note, doc_order):
|
|
|
1304
1311
|
|
|
1305
1312
|
ties = e.findall("tie")
|
|
1306
1313
|
if len(ties) > 0:
|
|
1307
|
-
|
|
1308
1314
|
tie_key = ("tie", getattr(note, "midi_pitch", "rest"))
|
|
1309
1315
|
tie_types = set(tie.attrib["type"] for tie in ties)
|
|
1310
1316
|
|
|
1311
1317
|
if "stop" in tie_types:
|
|
1312
|
-
|
|
1313
1318
|
tie_prev = ongoing.get(tie_key, None)
|
|
1314
1319
|
|
|
1315
1320
|
if tie_prev:
|
|
1316
|
-
|
|
1317
1321
|
note.tie_prev = tie_prev
|
|
1318
1322
|
tie_prev.tie_next = note
|
|
1319
1323
|
del ongoing[tie_key]
|
|
1320
1324
|
|
|
1321
1325
|
if "start" in tie_types:
|
|
1322
|
-
|
|
1323
1326
|
ongoing[tie_key] = note
|
|
1324
1327
|
|
|
1325
1328
|
notations = e.find("notations")
|
|
1326
1329
|
|
|
1327
1330
|
if notations is not None:
|
|
1328
|
-
|
|
1329
1331
|
if notations.find("fermata") is not None:
|
|
1330
|
-
|
|
1331
1332
|
fermata = score.Fermata(note)
|
|
1332
1333
|
part.add(fermata, position)
|
|
1333
1334
|
note.fermata = fermata
|
|
@@ -1337,21 +1338,17 @@ def _handle_note(e, position, part, ongoing, prev_note, doc_order):
|
|
|
1337
1338
|
)
|
|
1338
1339
|
|
|
1339
1340
|
for slur in starting_slurs:
|
|
1340
|
-
|
|
1341
1341
|
part.add(slur, position)
|
|
1342
1342
|
|
|
1343
1343
|
for slur in stopping_slurs:
|
|
1344
|
-
|
|
1345
1344
|
part.add(slur, end=position + duration)
|
|
1346
1345
|
|
|
1347
1346
|
starting_tups, stopping_tups = handle_tuplets(notations, ongoing, note)
|
|
1348
1347
|
|
|
1349
1348
|
for tup in starting_tups:
|
|
1350
|
-
|
|
1351
1349
|
part.add(tup, position)
|
|
1352
1350
|
|
|
1353
1351
|
for tup in stopping_tups:
|
|
1354
|
-
|
|
1355
1352
|
part.add(tup, end=position + duration)
|
|
1356
1353
|
|
|
1357
1354
|
new_position = position + duration
|
|
@@ -1377,31 +1374,26 @@ def handle_tuplets(notations, ongoing, note):
|
|
|
1377
1374
|
)
|
|
1378
1375
|
|
|
1379
1376
|
for tuplet_e in tuplets:
|
|
1380
|
-
|
|
1381
1377
|
tuplet_number = get_value_from_attribute(tuplet_e, "number", int) or note.voice
|
|
1382
1378
|
tuplet_type = get_value_from_attribute(tuplet_e, "type", str)
|
|
1383
1379
|
start_tuplet_key = ("start_tuplet", tuplet_number)
|
|
1384
1380
|
stop_tuplet_key = ("stop_tuplet", tuplet_number)
|
|
1385
1381
|
|
|
1386
1382
|
if tuplet_type == "start":
|
|
1387
|
-
|
|
1388
1383
|
# check if we have a stopped_tuplet in ongoing that corresponds to
|
|
1389
1384
|
# this start
|
|
1390
1385
|
tuplet = ongoing.pop(stop_tuplet_key, None)
|
|
1391
1386
|
|
|
1392
1387
|
if tuplet is None:
|
|
1393
|
-
|
|
1394
1388
|
tuplet = score.Tuplet(note)
|
|
1395
1389
|
ongoing[start_tuplet_key] = tuplet
|
|
1396
1390
|
|
|
1397
1391
|
else:
|
|
1398
|
-
|
|
1399
1392
|
tuplet.start_note = note
|
|
1400
1393
|
|
|
1401
1394
|
starting_tuplets.append(tuplet)
|
|
1402
1395
|
|
|
1403
1396
|
elif tuplet_type == "stop":
|
|
1404
|
-
|
|
1405
1397
|
tuplet = ongoing.pop(start_tuplet_key, None)
|
|
1406
1398
|
if tuplet is None:
|
|
1407
1399
|
# tuplet stop occurs before tuplet start in document order, that
|
|
@@ -1440,14 +1432,12 @@ def handle_slurs(notations, ongoing, note, position):
|
|
|
1440
1432
|
)
|
|
1441
1433
|
|
|
1442
1434
|
for slur_e in slurs:
|
|
1443
|
-
|
|
1444
1435
|
slur_number = get_value_from_attribute(slur_e, "number", int) or note.voice
|
|
1445
1436
|
slur_type = get_value_from_attribute(slur_e, "type", str)
|
|
1446
1437
|
start_slur_key = ("start_slur", slur_number)
|
|
1447
1438
|
stop_slur_key = ("stop_slur", slur_number)
|
|
1448
1439
|
|
|
1449
1440
|
if slur_type == "start":
|
|
1450
|
-
|
|
1451
1441
|
# check if we have a stopped_slur in ongoing that corresponds to
|
|
1452
1442
|
# this stop
|
|
1453
1443
|
slur = ongoing.pop(stop_slur_key, None)
|
|
@@ -1455,7 +1445,6 @@ def handle_slurs(notations, ongoing, note, position):
|
|
|
1455
1445
|
# if slur.end_note.start.t < position then the slur stop is
|
|
1456
1446
|
# rogue. We drop it and treat the slur start like a fresh start
|
|
1457
1447
|
if slur is None or slur.end_note.start.t < position:
|
|
1458
|
-
|
|
1459
1448
|
if slur and slur.end_note.start.t < position:
|
|
1460
1449
|
msg = (
|
|
1461
1450
|
"Dropping slur {} starting at {} ({}) and ending "
|
|
@@ -1477,17 +1466,14 @@ def handle_slurs(notations, ongoing, note, position):
|
|
|
1477
1466
|
ongoing[start_slur_key] = slur
|
|
1478
1467
|
|
|
1479
1468
|
else:
|
|
1480
|
-
|
|
1481
1469
|
slur.start_note = note
|
|
1482
1470
|
|
|
1483
1471
|
starting_slurs.append(slur)
|
|
1484
1472
|
|
|
1485
1473
|
elif slur_type == "stop":
|
|
1486
|
-
|
|
1487
1474
|
slur = ongoing.pop(start_slur_key, None)
|
|
1488
1475
|
|
|
1489
1476
|
if slur is None or slur.start_note.start.t > position:
|
|
1490
|
-
|
|
1491
1477
|
if slur and slur.start_note.start.t > position:
|
|
1492
1478
|
msg = (
|
|
1493
1479
|
"Dropping slur {} starting at {} ({}) and ending "
|
|
@@ -1511,7 +1497,6 @@ def handle_slurs(notations, ongoing, note, position):
|
|
|
1511
1497
|
ongoing[stop_slur_key] = slur
|
|
1512
1498
|
|
|
1513
1499
|
else:
|
|
1514
|
-
|
|
1515
1500
|
slur.end_note = note
|
|
1516
1501
|
|
|
1517
1502
|
stopping_slurs.append(slur)
|
|
@@ -62,7 +62,6 @@ def _load_csv(filename: PathLike) -> np.ndarray:
|
|
|
62
62
|
|
|
63
63
|
struct_array = np.empty(len(raw_array) - 1, dtype=dtypes)
|
|
64
64
|
for i, (c, dt) in enumerate(zip(columns, dtypes)):
|
|
65
|
-
|
|
66
65
|
if dt[-1] == "i4":
|
|
67
66
|
# Weird behavior trying to cast 0.0 as an integer
|
|
68
67
|
struct_array[c] = raw_array[1:, i].astype(float).astype(int)
|