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,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 %s" % msg)
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:
@@ -6,7 +6,6 @@ try:
6
6
  import music21 as m21
7
7
  from music21.stream import Score as M21Score
8
8
  except ImportError:
9
-
10
9
  m21 = None
11
10
 
12
11
  class M21Score(object):
@@ -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
- title = scid
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
- """ Parse a <measure>...</measure> element, adding it and its contents to the part.
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
- _add_tempo_if_unique(position, part, tempo)
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)