partitura 1.6.0__tar.gz → 1.7.0__tar.gz

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 (107) hide show
  1. {partitura-1.6.0 → partitura-1.7.0}/PKG-INFO +3 -2
  2. {partitura-1.6.0 → partitura-1.7.0}/partitura/assets/score_example.musicxml +3 -0
  3. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/exportmatch.py +35 -26
  4. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/exportmidi.py +13 -13
  5. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/exportmusicxml.py +33 -0
  6. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importdcml.py +1 -1
  7. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importmatch.py +42 -20
  8. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importmidi.py +32 -31
  9. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importmusicxml.py +50 -7
  10. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/performance_codec.py +20 -5
  11. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/performance_features.py +23 -2
  12. {partitura-1.6.0 → partitura-1.7.0}/partitura/performance.py +37 -15
  13. {partitura-1.6.0 → partitura-1.7.0}/partitura/score.py +211 -84
  14. {partitura-1.6.0 → partitura-1.7.0}/partitura/utils/__init__.py +0 -1
  15. {partitura-1.6.0 → partitura-1.7.0}/partitura/utils/generic.py +50 -45
  16. {partitura-1.6.0 → partitura-1.7.0}/partitura/utils/misc.py +2 -2
  17. {partitura-1.6.0 → partitura-1.7.0}/partitura/utils/music.py +322 -89
  18. {partitura-1.6.0 → partitura-1.7.0}/partitura.egg-info/PKG-INFO +3 -2
  19. {partitura-1.6.0 → partitura-1.7.0}/partitura.egg-info/SOURCES.txt +1 -0
  20. {partitura-1.6.0 → partitura-1.7.0}/setup.py +1 -1
  21. partitura-1.7.0/tests/test_iter.py +164 -0
  22. {partitura-1.6.0 → partitura-1.7.0}/tests/test_midi_import.py +136 -1
  23. partitura-1.7.0/tests/test_part_properties.py +103 -0
  24. {partitura-1.6.0 → partitura-1.7.0}/tests/test_performance.py +1 -1
  25. partitura-1.7.0/tests/test_transpose.py +43 -0
  26. {partitura-1.6.0 → partitura-1.7.0}/tests/test_utils.py +65 -1
  27. {partitura-1.6.0 → partitura-1.7.0}/tests/test_xml.py +25 -12
  28. partitura-1.6.0/tests/test_part_properties.py +0 -38
  29. partitura-1.6.0/tests/test_transpose.py +0 -26
  30. {partitura-1.6.0 → partitura-1.7.0}/LICENSE +0 -0
  31. {partitura-1.6.0 → partitura-1.7.0}/README.md +0 -0
  32. {partitura-1.6.0 → partitura-1.7.0}/partitura/__init__.py +0 -0
  33. {partitura-1.6.0 → partitura-1.7.0}/partitura/assets/musicxml.xsd +0 -0
  34. {partitura-1.6.0 → partitura-1.7.0}/partitura/assets/score_example.krn +0 -0
  35. {partitura-1.6.0 → partitura-1.7.0}/partitura/assets/score_example.mei +0 -0
  36. {partitura-1.6.0 → partitura-1.7.0}/partitura/assets/score_example.mid +0 -0
  37. {partitura-1.6.0 → partitura-1.7.0}/partitura/directions.py +0 -0
  38. {partitura-1.6.0 → partitura-1.7.0}/partitura/display.py +0 -0
  39. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/__init__.py +0 -0
  40. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/exportaudio.py +0 -0
  41. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/exportkern.py +0 -0
  42. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/exportmei.py +0 -0
  43. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/exportparangonada.py +0 -0
  44. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importkern.py +0 -0
  45. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importmei.py +0 -0
  46. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importmusic21.py +0 -0
  47. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importnakamura.py +0 -0
  48. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/importparangonada.py +0 -0
  49. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/matchfile_base.py +0 -0
  50. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/matchfile_utils.py +0 -0
  51. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/matchlines_v0.py +0 -0
  52. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/matchlines_v1.py +0 -0
  53. {partitura-1.6.0 → partitura-1.7.0}/partitura/io/musescore.py +0 -0
  54. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/__init__.py +0 -0
  55. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/key_identification.py +0 -0
  56. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/meter.py +0 -0
  57. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/note_array_to_score.py +0 -0
  58. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/note_features.py +0 -0
  59. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/pitch_spelling.py +0 -0
  60. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/tonal_tension.py +0 -0
  61. {partitura-1.6.0 → partitura-1.7.0}/partitura/musicanalysis/voice_separation.py +0 -0
  62. {partitura-1.6.0 → partitura-1.7.0}/partitura/utils/fluidsynth.py +0 -0
  63. {partitura-1.6.0 → partitura-1.7.0}/partitura/utils/globals.py +0 -0
  64. {partitura-1.6.0 → partitura-1.7.0}/partitura/utils/normalize.py +0 -0
  65. {partitura-1.6.0 → partitura-1.7.0}/partitura/utils/synth.py +0 -0
  66. {partitura-1.6.0 → partitura-1.7.0}/partitura.egg-info/dependency_links.txt +0 -0
  67. {partitura-1.6.0 → partitura-1.7.0}/partitura.egg-info/requires.txt +0 -0
  68. {partitura-1.6.0 → partitura-1.7.0}/partitura.egg-info/top_level.txt +0 -0
  69. {partitura-1.6.0 → partitura-1.7.0}/setup.cfg +0 -0
  70. {partitura-1.6.0 → partitura-1.7.0}/tests/test_clef.py +0 -0
  71. {partitura-1.6.0 → partitura-1.7.0}/tests/test_cross_staff.py +0 -0
  72. {partitura-1.6.0 → partitura-1.7.0}/tests/test_dcml_import.py +0 -0
  73. {partitura-1.6.0 → partitura-1.7.0}/tests/test_deprecations.py +0 -0
  74. {partitura-1.6.0 → partitura-1.7.0}/tests/test_display.py +0 -0
  75. {partitura-1.6.0 → partitura-1.7.0}/tests/test_fluidsynth.py +0 -0
  76. {partitura-1.6.0 → partitura-1.7.0}/tests/test_harmony.py +0 -0
  77. {partitura-1.6.0 → partitura-1.7.0}/tests/test_kern.py +0 -0
  78. {partitura-1.6.0 → partitura-1.7.0}/tests/test_key_estimation.py +0 -0
  79. {partitura-1.6.0 → partitura-1.7.0}/tests/test_load_performance.py +0 -0
  80. {partitura-1.6.0 → partitura-1.7.0}/tests/test_load_score.py +0 -0
  81. {partitura-1.6.0 → partitura-1.7.0}/tests/test_m21_import.py +0 -0
  82. {partitura-1.6.0 → partitura-1.7.0}/tests/test_match_export.py +0 -0
  83. {partitura-1.6.0 → partitura-1.7.0}/tests/test_match_import.py +0 -0
  84. {partitura-1.6.0 → partitura-1.7.0}/tests/test_mei.py +0 -0
  85. {partitura-1.6.0 → partitura-1.7.0}/tests/test_merge_parts.py +0 -0
  86. {partitura-1.6.0 → partitura-1.7.0}/tests/test_metrical_position.py +0 -0
  87. {partitura-1.6.0 → partitura-1.7.0}/tests/test_midi_export.py +0 -0
  88. {partitura-1.6.0 → partitura-1.7.0}/tests/test_musescore.py +0 -0
  89. {partitura-1.6.0 → partitura-1.7.0}/tests/test_nakamura.py +0 -0
  90. {partitura-1.6.0 → partitura-1.7.0}/tests/test_new_divs.py +0 -0
  91. {partitura-1.6.0 → partitura-1.7.0}/tests/test_note_array.py +0 -0
  92. {partitura-1.6.0 → partitura-1.7.0}/tests/test_note_features.py +0 -0
  93. {partitura-1.6.0 → partitura-1.7.0}/tests/test_octave_shift.py +0 -0
  94. {partitura-1.6.0 → partitura-1.7.0}/tests/test_parangonada.py +0 -0
  95. {partitura-1.6.0 → partitura-1.7.0}/tests/test_partial_measures.py +0 -0
  96. {partitura-1.6.0 → partitura-1.7.0}/tests/test_performance_codec.py +0 -0
  97. {partitura-1.6.0 → partitura-1.7.0}/tests/test_performance_features.py +0 -0
  98. {partitura-1.6.0 → partitura-1.7.0}/tests/test_pianoroll.py +0 -0
  99. {partitura-1.6.0 → partitura-1.7.0}/tests/test_pitch_spelling.py +0 -0
  100. {partitura-1.6.0 → partitura-1.7.0}/tests/test_quarter_adjust.py +0 -0
  101. {partitura-1.6.0 → partitura-1.7.0}/tests/test_rest_array.py +0 -0
  102. {partitura-1.6.0 → partitura-1.7.0}/tests/test_synth.py +0 -0
  103. {partitura-1.6.0 → partitura-1.7.0}/tests/test_time_estimation.py +0 -0
  104. {partitura-1.6.0 → partitura-1.7.0}/tests/test_times.py +0 -0
  105. {partitura-1.6.0 → partitura-1.7.0}/tests/test_tonal_tension.py +0 -0
  106. {partitura-1.6.0 → partitura-1.7.0}/tests/test_urlload.py +0 -0
  107. {partitura-1.6.0 → partitura-1.7.0}/tests/test_voice_estimation.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: partitura
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
4
  Summary: A package for handling symbolic musical information
5
5
  Home-page: https://github.com/CPJKU/partitura
6
6
  Author: Maarten Grachten, Carlos Cancino-Chacón, Silvan Peter, Emmanouil Karystinaios, Francesco Foscarin, Thassilo Gadermaier, Patricia Hu
@@ -29,6 +29,7 @@ Dynamic: description-content-type
29
29
  Dynamic: home-page
30
30
  Dynamic: keywords
31
31
  Dynamic: license
32
+ Dynamic: license-file
32
33
  Dynamic: requires-dist
33
34
  Dynamic: requires-python
34
35
  Dynamic: summary
@@ -13,6 +13,9 @@
13
13
  <measure number="1">
14
14
  <attributes>
15
15
  <divisions>12</divisions>
16
+ <key>
17
+ <fifths>0</fifths>
18
+ </key>
16
19
  <time>
17
20
  <beats>4</beats>
18
21
  <beat-type>4</beat-type>
@@ -70,7 +70,7 @@ def matchfile_from_alignment(
70
70
  piece: Optional[str] = None,
71
71
  score_filename: Optional[PathLike] = None,
72
72
  performance_filename: Optional[PathLike] = None,
73
- assume_part_unfolded: bool = False,
73
+ assume_part_unfolded: bool = True,
74
74
  tempo_indication: Optional[str] = None,
75
75
  diff_score_version_notes: Optional[list] = None,
76
76
  version: Version = LATEST_VERSION,
@@ -107,7 +107,7 @@ def matchfile_from_alignment(
107
107
  Whether to assume that the part has been unfolded according to the
108
108
  repetitions in the alignment. If False, the part will be automatically
109
109
  unfolded to have maximal coverage of the notes in the alignment.
110
- See `partitura.score.unfold_part_alignment`.
110
+ See `partitura.score.unfold_part_alignment`, defaults to True.
111
111
  tempo_indication : str or None
112
112
  The tempo direction indicated in the beginning of the score
113
113
  diff_score_version_notes : list or None
@@ -210,18 +210,28 @@ def matchfile_from_alignment(
210
210
  # Info for sorting lines
211
211
  snote_sort_info = dict()
212
212
  for (mnum, msd, msb), m in zip(measure_starts, measures):
213
+ if mnum == 0:
214
+ # handle offsets in anacrusis measure
215
+ ts_num, ts_den, _ = spart.time_signature_map(0)
216
+ dpq = int(spart.quarter_duration_map(0))
217
+ measure_dur_in_divs = m.end.t - m.start.t
218
+ expected_measure_dur = ts_num * 4 / ts_den * dpq
219
+ if measure_dur_in_divs < expected_measure_dur:
220
+ msd -= expected_measure_dur - measure_dur_in_divs
221
+ msb -= (expected_measure_dur - measure_dur_in_divs) / dpq * ts_den / 4
222
+
213
223
  time_signatures = spart.iter_all(score.TimeSignature, m.start, m.end)
214
224
 
215
225
  for tsig in time_signatures:
216
226
  time_divs = int(tsig.start.t)
217
227
  time_beats = float(beat_map(time_divs))
228
+ ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
218
229
  dpq = int(spart.quarter_duration_map(time_divs))
230
+ divs_per_beat = 4 / ts_den * dpq
219
231
  beat = int((time_beats - msb) // 1)
220
232
 
221
- ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
222
-
223
233
  moffset_divs = Fraction(
224
- int(time_divs - msd - beat * dpq), (int(ts_den) * dpq)
234
+ int(time_divs - msd - beat * divs_per_beat), int(ts_den * divs_per_beat)
225
235
  )
226
236
 
227
237
  scoreprop_lines["time_signatures"].append(
@@ -247,15 +257,15 @@ def matchfile_from_alignment(
247
257
  key_signatures = spart.iter_all(score.KeySignature, m.start, m.end)
248
258
 
249
259
  for ksig in key_signatures:
250
- time_divs = int(tsig.start.t)
260
+ time_divs = int(ksig.start.t)
251
261
  time_beats = float(beat_map(time_divs))
262
+ ts_num, ts_den, _ = spart.time_signature_map(ksig.start.t)
252
263
  dpq = int(spart.quarter_duration_map(time_divs))
264
+ divs_per_beat = 4 / ts_den * dpq
253
265
  beat = int((time_beats - msb) // 1)
254
266
 
255
- ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
256
-
257
267
  moffset_divs = Fraction(
258
- int(time_divs - msd - beat * dpq), (int(ts_den) * dpq)
268
+ int(time_divs - msd - beat * divs_per_beat), int(ts_den * divs_per_beat)
259
269
  )
260
270
 
261
271
  scoreprop_lines["key_signatures"].append(
@@ -282,27 +292,28 @@ def matchfile_from_alignment(
282
292
  snotes = spart.iter_all(score.Note, m.start, m.end, include_subclasses=True)
283
293
  # Beginning of each measure
284
294
  for snote in snotes:
285
- onset_divs, offset_divs = snote.start.t, snote.start.t + snote.duration_tied
295
+ onset_divs = snote.start.t
296
+ offset_divs = snote.start.t + snote.duration_tied
286
297
  duration_divs = offset_divs - onset_divs
287
-
298
+ # beat computations
288
299
  onset_beats, offset_beats = beat_map([onset_divs, offset_divs])
289
-
290
- dpq = int(spart.quarter_duration_map(onset_divs))
291
-
292
- beat = int((onset_beats - msb) // 1)
293
-
300
+ duration_beats = offset_beats - onset_beats
301
+ beat = int((onset_beats - msb) // 1) # beat field of the snote
302
+ # quarter, div, symbolic computation
294
303
  ts_num, ts_den, _ = spart.time_signature_map(snote.start.t)
295
-
296
- duration_symb = Fraction(duration_divs, dpq * 4)
297
-
298
- beat = int((onset_divs - msd) // dpq)
299
-
300
- moffset_divs = Fraction(int(onset_divs - msd - beat * dpq), (dpq * 4))
304
+ dpq = int(spart.quarter_duration_map(onset_divs))
305
+ duration_symb = Fraction(
306
+ duration_divs, dpq * 4
307
+ ) # compute duration from quarters/divs
308
+ divs_per_beat = 4 / ts_den * dpq
309
+ moffset_divs = Fraction(
310
+ int(onset_divs - msd - beat * divs_per_beat),
311
+ int(ts_den * divs_per_beat),
312
+ )
301
313
 
302
314
  if debug:
303
- duration_beats = offset_beats - onset_beats
304
315
  moffset_beat = (onset_beats - msb - beat) / ts_den
305
- assert np.isclose(float(duration_symb), duration_beats)
316
+ assert np.isclose(float(duration_symb), duration_beats / ts_den)
306
317
  assert np.isclose(moffset_beat, float(moffset_divs))
307
318
 
308
319
  score_attributes_list = []
@@ -508,9 +519,7 @@ def matchfile_from_alignment(
508
519
 
509
520
  # Concatenate all lines
510
521
  all_match_lines += list(note_lines) + pedal_lines
511
-
512
522
  matchfile = MatchFile(lines=all_match_lines)
513
-
514
523
  return matchfile
515
524
 
516
525
 
@@ -36,34 +36,34 @@ def map_to_track_channel(note_keys, mode):
36
36
  if mode == 0:
37
37
  trk = tr_helper.setdefault(p, len(tr_helper))
38
38
  ch1 = ch_helper.setdefault(p, {})
39
- ch2 = ch1.setdefault(v, len(ch1) + 1)
39
+ ch2 = ch1.setdefault(v, len(ch1))
40
40
  track[(pg, p, v)] = trk
41
41
  channel[(pg, p, v)] = ch2
42
42
  elif mode == 1:
43
43
  trk = tr_helper.setdefault(pg, len(tr_helper))
44
44
  ch1 = ch_helper.setdefault(pg, {})
45
- ch2 = ch1.setdefault(p, len(ch1) + 1)
45
+ ch2 = ch1.setdefault(p, len(ch1))
46
46
  track[(pg, p, v)] = trk
47
47
  channel[(pg, p, v)] = ch2
48
48
  elif mode == 2:
49
49
  track[(pg, p, v)] = 0
50
- ch = ch_helper.setdefault(p, len(ch_helper) + 1)
50
+ ch = ch_helper.setdefault(p, len(ch_helper))
51
51
  channel[(pg, p, v)] = ch
52
52
  elif mode == 3:
53
53
  trk = tr_helper.setdefault(p, len(tr_helper))
54
54
  track[(pg, p, v)] = trk
55
- channel[(pg, p, v)] = 1
55
+ channel[(pg, p, v)] = 0
56
56
  elif mode == 4:
57
57
  track[(pg, p, v)] = 0
58
- channel[(pg, p, v)] = 1
58
+ channel[(pg, p, v)] = 0
59
59
  elif mode == 5:
60
60
  trk = tr_helper.setdefault((p, v), len(tr_helper))
61
61
  track[(pg, p, v)] = trk
62
- channel[(pg, p, v)] = 1
62
+ channel[(pg, p, v)] = 0
63
63
  else:
64
64
  raise Exception("unsupported part/voice assign mode {}".format(mode))
65
65
 
66
- result = dict((k, (track.get(k, 0), channel.get(k, 1))) for k in note_keys)
66
+ result = dict((k, (track.get(k, 0), channel.get(k, 0))) for k in note_keys)
67
67
  # for (pg, p, voice), v in result.items():
68
68
  # pgn = pg.group_name if hasattr(pg, 'group_name') else pg.id
69
69
  # print(pgn, p.id, voice)
@@ -177,7 +177,7 @@ def save_performance_midi(
177
177
 
178
178
  for c in performed_part.controls:
179
179
  track = c.get("track", 0)
180
- ch = c.get("channel", 1)
180
+ ch = c.get("channel", 0)
181
181
  t = int(np.round(10**6 * ppq * c["time"] / mpq))
182
182
  track_events[track][t].append(
183
183
  Message(
@@ -190,7 +190,7 @@ def save_performance_midi(
190
190
 
191
191
  for n in performed_part.notes:
192
192
  track = n.get("track", 0)
193
- ch = n.get("channel", 1)
193
+ ch = n.get("channel", 0)
194
194
  t_on = int(np.round(10**6 * ppq * n["note_on"] / mpq))
195
195
  t_off = int(np.round(10**6 * ppq * n["note_off"] / mpq))
196
196
  vel = n.get("velocity", default_velocity)
@@ -203,7 +203,7 @@ def save_performance_midi(
203
203
 
204
204
  for p in performed_part.programs:
205
205
  track = p.get("track", 0)
206
- ch = p.get("channel", 1)
206
+ ch = p.get("channel", 0)
207
207
  t = int(np.round(10**6 * ppq * p["time"] / mpq))
208
208
  track_events[track][t].append(
209
209
  Message("program_change", program=int(p["program"]), channel=ch)
@@ -215,11 +215,11 @@ def save_performance_midi(
215
215
  list(
216
216
  set(
217
217
  [
218
- (c.get("channel", 1), c.get("track", 0))
218
+ (c.get("channel", 0), c.get("track", 0))
219
219
  for c in performed_part.controls
220
220
  ]
221
221
  + [
222
- (n.get("channel", 1), n.get("track", 0))
222
+ (n.get("channel", 0), n.get("track", 0))
223
223
  for n in performed_part.notes
224
224
  ]
225
225
  )
@@ -395,7 +395,7 @@ def save_score_midi(
395
395
 
396
396
  def to_ppq(t):
397
397
  # convert div times to new ppq
398
- return int(ppq * (qm(t) - ftp))
398
+ return round(ppq * (qm(t) - ftp))
399
399
 
400
400
  for tp in part.iter_all(score.Tempo):
401
401
  tempos[to_ppq(tp.start.t)] = MetaMessage(
@@ -36,6 +36,23 @@ ARTICULATIONS = [
36
36
  "tenuto",
37
37
  "unstress",
38
38
  ]
39
+ ORNAMENTS = [
40
+ "trill-mark",
41
+ "turn",
42
+ "delayed-turn",
43
+ "inverted-turn",
44
+ "delayed-inverted-turn",
45
+ "vertical-turn",
46
+ "inverted-vertical-turn",
47
+ "shake",
48
+ "wavy-line",
49
+ "mordent",
50
+ "inverted-mordent",
51
+ "schleifer",
52
+ "tremolo",
53
+ "haydn",
54
+ "other-ornament",
55
+ ]
39
56
 
40
57
 
41
58
  def range_number_from_counter(e, label, counter):
@@ -154,6 +171,16 @@ def make_note_el(note, dur, voice, counter, n_of_staves):
154
171
  articulations_e.extend(articulations)
155
172
  notations.append(articulations_e)
156
173
 
174
+ if note.ornaments:
175
+ ornaments = []
176
+ for ornament in note.ornaments:
177
+ if ornament in ORNAMENTS:
178
+ ornaments.append(etree.Element(ornament))
179
+ if ornaments:
180
+ ornaments_e = etree.Element("ornaments")
181
+ ornaments_e.extend(ornaments)
182
+ notations.append(ornaments_e)
183
+
157
184
  if note.technical:
158
185
  technical = []
159
186
  for technical_notation in note.technical:
@@ -184,6 +211,8 @@ def make_note_el(note, dur, voice, counter, n_of_staves):
184
211
  actual_e.text = str(sym_dur["actual_notes"])
185
212
  normal_e = etree.SubElement(time_mod_e, "normal-notes")
186
213
  normal_e.text = str(sym_dur["normal_notes"])
214
+ for _ in range(sym_dur.get("normal_dots", 0)):
215
+ etree.SubElement(time_mod_e, "normal-dot")
187
216
 
188
217
  if note.staff is not None:
189
218
  if note.staff != 1 or n_of_staves > 1:
@@ -240,12 +269,16 @@ def make_note_el(note, dur, voice, counter, n_of_staves):
240
269
  tuplet_actual_notes_e.text = str(tuplet.actual_notes)
241
270
  tuplet_actual_type_e = etree.SubElement(tuplet_actual_e, "tuplet-type")
242
271
  tuplet_actual_type_e.text = str(tuplet.actual_type)
272
+ for _ in range(tuplet.actual_dots):
273
+ etree.SubElement(tuplet_actual_e, "tuplet-dot")
243
274
  # tuplet-normal tag
244
275
  tuplet_normal_e = etree.SubElement(tuplet_e, "tuplet-normal")
245
276
  tuplet_normal_notes_e = etree.SubElement(tuplet_normal_e, "tuplet-number")
246
277
  tuplet_normal_notes_e.text = str(tuplet.normal_notes)
247
278
  tuplet_normal_type_e = etree.SubElement(tuplet_normal_e, "tuplet-type")
248
279
  tuplet_normal_type_e.text = str(tuplet.normal_type)
280
+ for _ in range(tuplet.normal_dots):
281
+ etree.SubElement(tuplet_normal_e, "tuplet-dot")
249
282
  notations.append(tuplet_e)
250
283
 
251
284
  if notations:
@@ -284,7 +284,7 @@ def read_harmony_tsv(beat_tsv_path, part):
284
284
  # key_alter = re.search(r"[#b]", row["globalkey"]).group(0) if re.search(r"[#b]", row["globalkey"]) else ""
285
285
  # key_alter = key_alter.replace("b", "-")
286
286
  # key_alter = ALT_TO_INT[key_alter]
287
- # key_step, key_alter = transpose_note(key_step, key_alter, transposition_interval)
287
+ # key_step, key_alter, _ = transpose_note_attributes(transposition_interval, key_step, key_alter)
288
288
  # local_key = key_step + INT_TO_ALT[key_alter]
289
289
  part.add(
290
290
  spt.Cadence(
@@ -226,8 +226,7 @@ def load_match(
226
226
  ppart = performed_part_from_match(mf, pedal_threshold, first_note_at_zero)
227
227
 
228
228
  performance = Performance(
229
- id=get_document_name(filename),
230
- performedparts=ppart,
229
+ id=get_document_name(filename), performedparts=ppart, ensure_unique_tracks=False
231
230
  )
232
231
  # Generate Part
233
232
  if create_score:
@@ -345,10 +344,16 @@ def performed_part_from_match(
345
344
  notes = list()
346
345
  note_onsets_in_secs = np.array(np.zeros(len(mf.notes)), dtype=float)
347
346
  note_onsets_in_tick = np.array(np.zeros(len(mf.notes)), dtype=int)
347
+ tracks = set()
348
+ channels = set()
348
349
  for i, note in enumerate(mf.notes):
349
350
  n_onset_sec = midi_ticks_to_seconds(note.Onset, mpq, ppq)
350
351
  note_onsets_in_secs[i] = n_onset_sec
351
352
  note_onsets_in_tick[i] = note.Onset
353
+ track = getattr(note, "Track", 0)
354
+ tracks.add(track)
355
+ channel = getattr(note, "Channel", 0)
356
+ channels.add(channel)
352
357
  notes.append(
353
358
  dict(
354
359
  id=format_pnote_id(note.Id),
@@ -359,8 +364,8 @@ def performed_part_from_match(
359
364
  note_off_tick=note.Offset,
360
365
  sound_off=midi_ticks_to_seconds(note.Offset, mpq, ppq),
361
366
  velocity=note.Velocity,
362
- track=getattr(note, "Track", 0),
363
- channel=getattr(note, "Channel", 0),
367
+ track=track,
368
+ channel=channel,
364
369
  )
365
370
  )
366
371
  # Set first note_on to zero in ticks and seconds if first_note_at_zero
@@ -375,12 +380,23 @@ def performed_part_from_match(
375
380
  note["note_on_tick"] -= offset_tick
376
381
  note["note_off_tick"] -= offset_tick
377
382
 
383
+ # check if multiple tracks are in the match file
384
+ if len(tracks) > 1:
385
+ warnings.warn("Notes on multiple MIDI tracks in matchfile" "information!.")
386
+ used_track = min(tracks)
387
+ # check if multiple tracks are in the match file
388
+ if len(channels) > 1:
389
+ warnings.warn("Notes on multiple MIDI channels in matchfile" "information!.")
390
+ used_channel = min(channels)
391
+
378
392
  # SustainPedal instances for sustain pedal lines
379
393
  sustain_pedal = [
380
394
  dict(
381
395
  number=64,
382
396
  time=midi_ticks_to_seconds(ped.Time, mpq, ppq),
383
397
  value=ped.Value,
398
+ track=used_track,
399
+ channel=used_channel,
384
400
  )
385
401
  for ped in mf.sustain_pedal
386
402
  ]
@@ -391,6 +407,8 @@ def performed_part_from_match(
391
407
  number=67,
392
408
  time=midi_ticks_to_seconds(ped.Time, mpq, ppq),
393
409
  value=ped.Value,
410
+ track=used_track,
411
+ channel=used_channel,
394
412
  )
395
413
  for ped in mf.soft_pedal
396
414
  ]
@@ -402,6 +420,9 @@ def performed_part_from_match(
402
420
  notes=notes,
403
421
  controls=sustain_pedal + soft_pedal,
404
422
  sustain_pedal_threshold=pedal_threshold,
423
+ ppq=ppq,
424
+ mpq=mpq,
425
+ track=used_track,
405
426
  )
406
427
  return ppart
407
428
 
@@ -458,9 +479,9 @@ def part_from_matchfile(
458
479
  max_time = max(n.OffsetInBeats for n in snotes)
459
480
  (
460
481
  beats_map_from_beats,
461
- beats_map,
482
+ beats_map_from_quarters,
462
483
  beat_type_map_from_beats,
463
- beat_type_map,
484
+ beat_type_map_from_quarters,
464
485
  min_time_q,
465
486
  max_time_q,
466
487
  ) = make_timesig_maps(ts, max_time)
@@ -468,13 +489,13 @@ def part_from_matchfile(
468
489
  # compute necessary divs based on the types of notes in the
469
490
  # match snotes (only integers)
470
491
  divs_arg = [
471
- max(int((beat_type_map(note.OnsetInBeats) / 4)), 1)
492
+ max(int((beat_type_map_from_beats(note.OnsetInBeats) / 4)), 1)
472
493
  * note.Offset.denominator
473
494
  * (note.Offset.tuple_div or 1)
474
495
  for note in snotes
475
496
  ]
476
497
  divs_arg += [
477
- max(int((beat_type_map(note.OnsetInBeats) / 4)), 1)
498
+ max(int((beat_type_map_from_beats(note.OnsetInBeats) / 4)), 1)
478
499
  * note.Duration.denominator
479
500
  * (note.Duration.tuple_div or 1)
480
501
  for note in snotes
@@ -484,24 +505,24 @@ def part_from_matchfile(
484
505
  unique_onsets, inv_idxs = np.unique(onset_in_beats, return_inverse=True)
485
506
 
486
507
  iois_in_beats = np.diff(unique_onsets)
487
- beat_to_quarter = 4 / beat_type_map(onset_in_beats)
508
+ beat_to_quarter = 4 / beat_type_map_from_beats(onset_in_beats)
488
509
 
489
510
  iois_in_quarters_offset = np.r_[
490
511
  beat_to_quarter[0] * onset_in_beats[0],
491
- (4 / beat_type_map(unique_onsets[:-1])) * iois_in_beats,
512
+ (4 / beat_type_map_from_beats(unique_onsets[:-1])) * iois_in_beats,
492
513
  ]
493
514
  onset_in_quarters = np.cumsum(iois_in_quarters_offset)
494
515
  iois_in_quarters = np.diff(onset_in_quarters)
495
516
 
496
517
  # ___ these divs are relative to quarters;
497
518
  divs = np.lcm.reduce(np.unique(divs_arg))
498
- onset_in_divs = np.r_[0, np.cumsum(divs * iois_in_quarters)][inv_idxs]
519
+ onset_in_divs = np.r_[0, np.cumsum(divs * iois_in_quarters, dtype=int)][inv_idxs]
499
520
  onset_in_quarters = onset_in_quarters[inv_idxs]
500
521
 
501
522
  part.set_quarter_duration(0, divs)
502
523
  bars = np.unique([n.Measure for n in snotes])
503
524
  t = min_time
504
- t = t * 4 / beat_type_map(min_time)
525
+ t = t * 4 / beat_type_map_from_beats(min_time)
505
526
  offset = t
506
527
  bar_times = {}
507
528
 
@@ -511,9 +532,9 @@ def part_from_matchfile(
511
532
  # if starting beat is above zero, add padding
512
533
  rest = score.Rest()
513
534
  part.add(rest, start=0, end=t * divs)
514
- onset_in_divs += t * divs
535
+ onset_in_divs += round(t * divs)
515
536
  offset = 0
516
- t = t - t % beats_map(min_time)
537
+ t = t - t % beats_map_from_beats(min_time)
517
538
 
518
539
  for b_name in bars:
519
540
  notes_in_this_bar = [
@@ -552,14 +573,14 @@ def part_from_matchfile(
552
573
  # on_off_scale = 1 means duration and beat offset are given in
553
574
  # whole notes, else they're given in beats (as in the KAIST data)
554
575
  if not match_offset_duration_in_whole:
555
- on_off_scale = beat_type_map(bar_start)
576
+ on_off_scale = beat_type_map_from_quarters(bar_start)
556
577
 
557
578
  # offset within bar in quarter units adjusted for different
558
- # time signatures -> 4 / beat_type_map(bar_start)
559
- bar_offset = (note.Beat - 1) * 4 / beat_type_map(bar_start)
579
+ # time signatures -> 4 / beat_type_map_from_quarters(bar_start)
580
+ bar_offset = (note.Beat - 1) * 4 / beat_type_map_from_quarters(bar_start)
560
581
 
561
582
  # offset within beat in quarter units adjusted for different
562
- # time signatures -> 4 / beat_type_map(bar_start)
583
+ # time signatures -> 4 / beat_type_map_from_quarters(bar_start)
563
584
  beat_offset = (
564
585
  4
565
586
  / on_off_scale
@@ -576,6 +597,7 @@ def part_from_matchfile(
576
597
  "Calculated `onset_divs` does not match `OnsetInBeats` " "information!."
577
598
  )
578
599
  onset_divs = onset_in_divs[ni]
600
+
579
601
  assert onset_divs >= 0
580
602
  assert np.isclose(onset_divs, onset_in_divs[ni], atol=divs * 0.01)
581
603
  is_tied = False
@@ -753,9 +775,9 @@ def part_from_matchfile(
753
775
  last_closing_barline = barline_in_divs + int(
754
776
  round(
755
777
  divs
756
- * beats_map(barline_in_quarters)
778
+ * beats_map_from_quarters(barline_in_quarters)
757
779
  * 4
758
- / beat_type_map(barline_in_quarters)
780
+ / beat_type_map_from_quarters(barline_in_quarters)
759
781
  )
760
782
  )
761
783
  part.add(prev_measure, None, last_closing_barline)
@@ -6,6 +6,7 @@ This module contains methods for importing MIDI files.
6
6
  import warnings
7
7
 
8
8
  from collections import defaultdict
9
+ from operator import itemgetter
9
10
  from typing import Union, Optional, List, Tuple, Dict
10
11
  import numpy as np
11
12
 
@@ -259,6 +260,7 @@ def load_performance_midi(
259
260
  velocity=sounding_notes[note][2],
260
261
  )
261
262
  )
263
+
262
264
  # remove hash from dict
263
265
  del sounding_notes[note]
264
266
 
@@ -274,6 +276,25 @@ def load_performance_midi(
274
276
  )
275
277
  )
276
278
 
279
+ # adjust timing of events based on tempo changes
280
+ for note in notes:
281
+ note["note_on"] = adjust_time(note["note_on_tick"], tempo_changes, ppq)
282
+ note["note_off"] = adjust_time(note["note_off_tick"], tempo_changes, ppq)
283
+ for control in controls:
284
+ control["time"] = adjust_time(control["time_tick"], tempo_changes, ppq)
285
+ for program in programs:
286
+ program["time"] = adjust_time(program["time_tick"], tempo_changes, ppq)
287
+ for time_signature in time_signatures:
288
+ time_signature["time"] = adjust_time(
289
+ time_signature["time_tick"], tempo_changes, ppq
290
+ )
291
+ for key_signature in key_signatures:
292
+ key_signature["time"] = adjust_time(
293
+ key_signature["time_tick"], tempo_changes, ppq
294
+ )
295
+ for meta in meta_other:
296
+ meta["time"] = adjust_time(meta["time_tick"], tempo_changes, ppq)
297
+
277
298
  # add note id to every note
278
299
  for k, note in enumerate(notes):
279
300
  note["id"] = f"n{k}"
@@ -290,33 +311,13 @@ def load_performance_midi(
290
311
  mpq=default_mpq,
291
312
  track=i,
292
313
  )
293
-
294
314
  pps.append(pp)
295
315
 
296
- # adjust timing of events based on tempo changes
297
- for pp in pps:
298
- for note in pp.notes:
299
- note["note_on"] = adjust_time(note["note_on_tick"], tempo_changes, ppq)
300
- note["note_off"] = adjust_time(note["note_off_tick"], tempo_changes, ppq)
301
- for control in pp.controls:
302
- control["time"] = adjust_time(control["time_tick"], tempo_changes, ppq)
303
- for program in pp.programs:
304
- program["time"] = adjust_time(program["time_tick"], tempo_changes, ppq)
305
- for time_signature in pp.time_signatures:
306
- time_signature["time"] = adjust_time(
307
- time_signature["time_tick"], tempo_changes, ppq
308
- )
309
- for key_signature in pp.key_signatures:
310
- key_signature["time"] = adjust_time(
311
- key_signature["time_tick"], tempo_changes, ppq
312
- )
313
- for meta in pp.meta_other:
314
- meta["time"] = adjust_time(meta["time_tick"], tempo_changes, ppq)
315
-
316
316
  perf = performance.Performance(
317
317
  id=doc_name,
318
318
  performedparts=pps,
319
319
  )
320
+
320
321
  return perf
321
322
 
322
323
 
@@ -618,23 +619,25 @@ or a list of these
618
619
  # now clear all track_ts
619
620
  time_sigs_by_track.clear()
620
621
 
621
- time_sigs_by_part = defaultdict(set)
622
+ # use a list to preserve all time sigs in order
623
+ time_sigs_by_part = defaultdict(list)
622
624
  for tr, ts_list in time_sigs_by_track.items():
623
625
  for ts in ts_list:
624
626
  for part in track_to_part_mapping[tr]:
625
- time_sigs_by_part[part].add(ts)
627
+ time_sigs_by_part[part].append(ts)
626
628
  for ts in global_time_sigs:
627
629
  for part in set(part for _, part, _ in group_part_voice_keys):
628
- time_sigs_by_part[part].add(ts)
630
+ time_sigs_by_part[part].append(ts)
629
631
 
630
- key_sigs_by_part = defaultdict(set)
632
+ # use a list to preserve all key sigs in order
633
+ key_sigs_by_part = defaultdict(list)
631
634
  for tr, ks_list in key_sigs_by_track.items():
632
635
  for ks in ks_list:
633
636
  for part in track_to_part_mapping[tr]:
634
- key_sigs_by_part[part].add(ks)
637
+ key_sigs_by_part[part].append(ks)
635
638
  for ks in global_key_sigs:
636
639
  for part in set(part for _, part, _ in group_part_voice_keys):
637
- key_sigs_by_part[part].add(ks)
640
+ key_sigs_by_part[part].append(ks)
638
641
 
639
642
  # names_by_part = defaultdict(set)
640
643
  # for tr_ch, pg_p_v in zip(tr_ch_keys, group_part_voice_keys):
@@ -661,13 +664,11 @@ or a list of these
661
664
  spellings,
662
665
  voices,
663
666
  note_ids,
664
- sorted(time_sigs_by_part[part_nr]),
665
- sorted(key_sigs_by_part[part_nr]),
667
+ sorted(time_sigs_by_part[part_nr], key=itemgetter(0)),
668
+ sorted(key_sigs_by_part[part_nr], key=itemgetter(0)),
666
669
  part_id="P{}".format(part_nr + 1),
667
670
  part_name=part_names.get(part_nr, None),
668
671
  )
669
-
670
- # print(part.pretty())
671
672
  # if this part has an associated part_group number we create a PartGroup
672
673
  # if necessary, and add the part to that. The newly created PartGroup is
673
674
  # then added to the partlist.