partitura 1.5.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.
- {partitura-1.5.0 → partitura-1.7.0}/PKG-INFO +14 -2
- partitura-1.7.0/partitura/assets/score_example.mei +69 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/assets/score_example.musicxml +3 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/__init__.py +36 -8
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/exportkern.py +1 -1
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/exportmatch.py +42 -28
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/exportmei.py +35 -8
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/exportmidi.py +13 -13
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/exportmusicxml.py +88 -10
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importdcml.py +1 -1
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importkern.py +195 -79
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importmatch.py +128 -104
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importmei.py +48 -12
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importmidi.py +83 -31
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importmusicxml.py +220 -14
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importnakamura.py +1 -1
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/matchfile_utils.py +2 -1
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/note_array_to_score.py +1 -1
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/note_features.py +138 -8
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/performance_codec.py +53 -27
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/performance_features.py +24 -3
- {partitura-1.5.0 → partitura-1.7.0}/partitura/performance.py +68 -22
- {partitura-1.5.0 → partitura-1.7.0}/partitura/score.py +701 -380
- {partitura-1.5.0 → partitura-1.7.0}/partitura/utils/__init__.py +4 -1
- {partitura-1.5.0 → partitura-1.7.0}/partitura/utils/generic.py +78 -41
- {partitura-1.5.0 → partitura-1.7.0}/partitura/utils/globals.py +200 -2
- {partitura-1.5.0 → partitura-1.7.0}/partitura/utils/misc.py +24 -3
- {partitura-1.5.0 → partitura-1.7.0}/partitura/utils/music.py +355 -94
- {partitura-1.5.0 → partitura-1.7.0}/partitura.egg-info/PKG-INFO +14 -2
- {partitura-1.5.0 → partitura-1.7.0}/partitura.egg-info/SOURCES.txt +4 -1
- {partitura-1.5.0 → partitura-1.7.0}/setup.py +1 -1
- partitura-1.7.0/tests/test_clef.py +105 -0
- partitura-1.7.0/tests/test_cross_staff.py +34 -0
- partitura-1.7.0/tests/test_iter.py +164 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_mei.py +8 -7
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_merge_parts.py +10 -1
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_metrical_position.py +2 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_midi_import.py +196 -3
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_note_features.py +17 -1
- partitura-1.7.0/tests/test_part_properties.py +103 -0
- partitura-1.7.0/tests/test_performance.py +332 -0
- partitura-1.7.0/tests/test_performance_features.py +46 -0
- partitura-1.7.0/tests/test_transpose.py +43 -0
- partitura-1.7.0/tests/test_urlload.py +26 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_utils.py +130 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_xml.py +77 -0
- partitura-1.5.0/partitura/assets/score_example.mei +0 -56
- partitura-1.5.0/tests/test_cross_staff_beaming.py +0 -20
- partitura-1.5.0/tests/test_part_properties.py +0 -38
- partitura-1.5.0/tests/test_performance.py +0 -140
- partitura-1.5.0/tests/test_performance_features.py +0 -42
- partitura-1.5.0/tests/test_transpose.py +0 -26
- {partitura-1.5.0 → partitura-1.7.0}/LICENSE +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/README.md +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/__init__.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/assets/musicxml.xsd +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/assets/score_example.krn +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/assets/score_example.mid +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/directions.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/display.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/exportaudio.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/exportparangonada.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importmusic21.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/importparangonada.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/matchfile_base.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/matchlines_v0.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/matchlines_v1.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/io/musescore.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/__init__.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/key_identification.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/meter.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/pitch_spelling.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/tonal_tension.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/musicanalysis/voice_separation.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/utils/fluidsynth.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/utils/normalize.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura/utils/synth.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura.egg-info/dependency_links.txt +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura.egg-info/requires.txt +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/partitura.egg-info/top_level.txt +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/setup.cfg +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_dcml_import.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_deprecations.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_display.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_fluidsynth.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_harmony.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_kern.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_key_estimation.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_load_performance.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_load_score.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_m21_import.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_match_export.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_match_import.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_midi_export.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_musescore.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_nakamura.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_new_divs.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_note_array.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_octave_shift.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_parangonada.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_partial_measures.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_performance_codec.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_pianoroll.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_pitch_spelling.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_quarter_adjust.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_rest_array.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_synth.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_time_estimation.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_times.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_tonal_tension.py +0 -0
- {partitura-1.5.0 → partitura-1.7.0}/tests/test_voice_estimation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: partitura
|
|
3
|
-
Version: 1.
|
|
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
|
|
@@ -21,6 +21,18 @@ Requires-Dist: lxml
|
|
|
21
21
|
Requires-Dist: lark-parser
|
|
22
22
|
Requires-Dist: xmlschema
|
|
23
23
|
Requires-Dist: mido
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: author-email
|
|
26
|
+
Dynamic: classifier
|
|
27
|
+
Dynamic: description
|
|
28
|
+
Dynamic: description-content-type
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
24
36
|
|
|
25
37
|
|
|
26
38
|
[//]: # (<p align="center"> )
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<?xml-model href="https://music-encoding.org/schema/5.0/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
|
|
3
|
+
<?xml-model href="https://music-encoding.org/schema/5.0/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
|
|
4
|
+
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.0">
|
|
5
|
+
<meiHead xml:id="m21agkk">
|
|
6
|
+
<fileDesc xml:id="f1x9ud6p">
|
|
7
|
+
<titleStmt xml:id="t1pe4slb">
|
|
8
|
+
<title />
|
|
9
|
+
<respStmt />
|
|
10
|
+
</titleStmt>
|
|
11
|
+
<pubStmt xml:id="p1kxcrok">
|
|
12
|
+
<date isodate="2024-10-30" type="encoding-date">2024-10-30</date>
|
|
13
|
+
</pubStmt>
|
|
14
|
+
</fileDesc>
|
|
15
|
+
<encodingDesc xml:id="e1xr2zpp">
|
|
16
|
+
<appInfo xml:id="a1wyxc5n">
|
|
17
|
+
<application xml:id="a1t43ge2" isodate="2024-10-30T10:56:32" version="4.3.1-3b8cc17">
|
|
18
|
+
<name xml:id="n1cp60l4">Verovio</name>
|
|
19
|
+
<p xml:id="p13nndma">Transcoded from MusicXML</p>
|
|
20
|
+
</application>
|
|
21
|
+
</appInfo>
|
|
22
|
+
</encodingDesc>
|
|
23
|
+
</meiHead>
|
|
24
|
+
<music>
|
|
25
|
+
<body>
|
|
26
|
+
<mdiv xml:id="muo97v6">
|
|
27
|
+
<score xml:id="suneqlv">
|
|
28
|
+
<scoreDef xml:id="sz00r05">
|
|
29
|
+
<staffGrp xml:id="s1h35kps">
|
|
30
|
+
<staffGrp xml:id="P1" bar.thru="true">
|
|
31
|
+
<grpSym xml:id="g1eji31e" symbol="brace" />
|
|
32
|
+
<label xml:id="l8lirjj">Piano</label>
|
|
33
|
+
<instrDef xml:id="iggd40h" midi.channel="0" midi.instrnum="0" midi.volume="78.00%" />
|
|
34
|
+
<staffDef xml:id="sbpks8p" n="1" lines="5" ppq="1">
|
|
35
|
+
<clef xml:id="c1p7nrnw" shape="G" line="2" />
|
|
36
|
+
<keySig xml:id="ki5anqv" sig="0" />
|
|
37
|
+
<meterSig xml:id="m1vpw2w2" count="4" unit="4" />
|
|
38
|
+
</staffDef>
|
|
39
|
+
<staffDef xml:id="s1g416nz" n="2" lines="5" ppq="1">
|
|
40
|
+
<clef xml:id="cezkswz" shape="G" line="2" />
|
|
41
|
+
<keySig xml:id="kk41xfz" sig="0" />
|
|
42
|
+
<meterSig xml:id="m7alo7s" count="4" unit="4" />
|
|
43
|
+
</staffDef>
|
|
44
|
+
</staffGrp>
|
|
45
|
+
</staffGrp>
|
|
46
|
+
</scoreDef>
|
|
47
|
+
<section xml:id="suu4o7p">
|
|
48
|
+
<measure xml:id="m1e358qx" n="1">
|
|
49
|
+
<staff xml:id="s1ufvigy" n="1">
|
|
50
|
+
<layer xml:id="l1r7cvga" n="1">
|
|
51
|
+
<rest xml:id="ro1o7cb" dur.ppq="2" dur="2" />
|
|
52
|
+
<chord xml:id="c1c1r6b7" dur.ppq="2" dur="2" stem.dir="down">
|
|
53
|
+
<note xml:id="n6dpu2p" oct="5" pname="c" />
|
|
54
|
+
<note xml:id="njfgcwp" oct="5" pname="e" />
|
|
55
|
+
</chord>
|
|
56
|
+
</layer>
|
|
57
|
+
</staff>
|
|
58
|
+
<staff xml:id="s710zw2" n="2">
|
|
59
|
+
<layer xml:id="leffrs2" n="5">
|
|
60
|
+
<note xml:id="n1txt37q" dur.ppq="4" dur="1" oct="4" pname="a" />
|
|
61
|
+
</layer>
|
|
62
|
+
</staff>
|
|
63
|
+
</measure>
|
|
64
|
+
</section>
|
|
65
|
+
</score>
|
|
66
|
+
</mdiv>
|
|
67
|
+
</body>
|
|
68
|
+
</music>
|
|
69
|
+
</mei>
|
|
@@ -5,7 +5,9 @@ This module contains methods for importing and exporting symbolic music formats.
|
|
|
5
5
|
"""
|
|
6
6
|
from typing import Union
|
|
7
7
|
import os
|
|
8
|
-
|
|
8
|
+
import urllib.request
|
|
9
|
+
from urllib.parse import urlparse
|
|
10
|
+
import tempfile
|
|
9
11
|
from .importmusicxml import load_musicxml
|
|
10
12
|
from .importmidi import load_score_midi, load_performance_midi
|
|
11
13
|
from .musescore import load_via_musescore
|
|
@@ -32,6 +34,14 @@ class NotSupportedFormatError(Exception):
|
|
|
32
34
|
pass
|
|
33
35
|
|
|
34
36
|
|
|
37
|
+
def is_url(input):
|
|
38
|
+
try:
|
|
39
|
+
result = urlparse(input)
|
|
40
|
+
return all([result.scheme, result.netloc])
|
|
41
|
+
except ValueError:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
|
|
35
45
|
@deprecated_alias(score_fn="filename")
|
|
36
46
|
@deprecated_parameter("ensure_list")
|
|
37
47
|
def load_score(filename: PathLike, force_note_ids="keep") -> Score:
|
|
@@ -57,11 +67,29 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score:
|
|
|
57
67
|
scr: :class:`partitura.score.Score`
|
|
58
68
|
A score instance.
|
|
59
69
|
"""
|
|
70
|
+
if is_url(filename):
|
|
71
|
+
url = filename
|
|
72
|
+
# Send a GET request to the URL
|
|
73
|
+
with urllib.request.urlopen(url) as response:
|
|
74
|
+
data = response.read()
|
|
75
|
+
|
|
76
|
+
# Extract the file extension from the URL
|
|
77
|
+
extension = os.path.splitext(url)[-1]
|
|
78
|
+
|
|
79
|
+
# Create a temporary file
|
|
80
|
+
temp_file = tempfile.NamedTemporaryFile(suffix=extension, delete=True)
|
|
81
|
+
|
|
82
|
+
# Write the content to the temporary file
|
|
83
|
+
with open(temp_file.name, "wb") as f:
|
|
84
|
+
f.write(data)
|
|
85
|
+
|
|
86
|
+
filename = temp_file.name
|
|
87
|
+
else:
|
|
88
|
+
extension = os.path.splitext(filename)[-1].lower()
|
|
60
89
|
|
|
61
|
-
extension = os.path.splitext(filename)[-1].lower()
|
|
62
90
|
if extension in (".mxl", ".xml", ".musicxml"):
|
|
63
91
|
# Load MusicXML
|
|
64
|
-
|
|
92
|
+
score = load_musicxml(
|
|
65
93
|
filename=filename,
|
|
66
94
|
force_note_ids=force_note_ids,
|
|
67
95
|
)
|
|
@@ -71,15 +99,15 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score:
|
|
|
71
99
|
assign_note_ids = False
|
|
72
100
|
else:
|
|
73
101
|
assign_note_ids = True
|
|
74
|
-
|
|
102
|
+
score = load_score_midi(
|
|
75
103
|
filename=filename,
|
|
76
104
|
assign_note_ids=assign_note_ids,
|
|
77
105
|
)
|
|
78
106
|
elif extension in [".mei"]:
|
|
79
107
|
# Load MEI
|
|
80
|
-
|
|
108
|
+
score = load_mei(filename=filename)
|
|
81
109
|
elif extension in [".kern", ".krn"]:
|
|
82
|
-
|
|
110
|
+
score = load_kern(
|
|
83
111
|
filename=filename,
|
|
84
112
|
force_note_ids=force_note_ids,
|
|
85
113
|
)
|
|
@@ -107,7 +135,7 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score:
|
|
|
107
135
|
".gp",
|
|
108
136
|
]:
|
|
109
137
|
# Load MuseScore
|
|
110
|
-
|
|
138
|
+
score = load_via_musescore(
|
|
111
139
|
filename=filename,
|
|
112
140
|
force_note_ids=force_note_ids,
|
|
113
141
|
)
|
|
@@ -117,11 +145,11 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score:
|
|
|
117
145
|
filename=filename,
|
|
118
146
|
create_score=True,
|
|
119
147
|
)
|
|
120
|
-
return score
|
|
121
148
|
else:
|
|
122
149
|
raise NotSupportedFormatError(
|
|
123
150
|
f"{extension} file extension is not supported. If this should be supported, consider editing partitura/io/__init__.py file"
|
|
124
151
|
)
|
|
152
|
+
return score
|
|
125
153
|
|
|
126
154
|
|
|
127
155
|
def load_score_as_part(filename: PathLike) -> Part:
|
|
@@ -327,7 +327,7 @@ def save_kern(
|
|
|
327
327
|
out_data = exporter.parse()
|
|
328
328
|
out_data = exporter.trim(out_data)
|
|
329
329
|
# Use numpy savetxt to save the file
|
|
330
|
-
footer = "Encoded using the Partitura Python package, version 1.
|
|
330
|
+
footer = "Encoded using the Partitura Python package, version 1.6.0"
|
|
331
331
|
if out is not None:
|
|
332
332
|
np.savetxt(
|
|
333
333
|
fname=out,
|
|
@@ -21,7 +21,6 @@ from partitura.performance import Performance, PerformedPart, PerformanceLike
|
|
|
21
21
|
from partitura.io.matchlines_v1 import (
|
|
22
22
|
make_info,
|
|
23
23
|
make_scoreprop,
|
|
24
|
-
make_section,
|
|
25
24
|
MatchSnote,
|
|
26
25
|
MatchNote,
|
|
27
26
|
MatchSnoteNote,
|
|
@@ -71,7 +70,7 @@ def matchfile_from_alignment(
|
|
|
71
70
|
piece: Optional[str] = None,
|
|
72
71
|
score_filename: Optional[PathLike] = None,
|
|
73
72
|
performance_filename: Optional[PathLike] = None,
|
|
74
|
-
assume_part_unfolded: bool =
|
|
73
|
+
assume_part_unfolded: bool = True,
|
|
75
74
|
tempo_indication: Optional[str] = None,
|
|
76
75
|
diff_score_version_notes: Optional[list] = None,
|
|
77
76
|
version: Version = LATEST_VERSION,
|
|
@@ -108,7 +107,7 @@ def matchfile_from_alignment(
|
|
|
108
107
|
Whether to assume that the part has been unfolded according to the
|
|
109
108
|
repetitions in the alignment. If False, the part will be automatically
|
|
110
109
|
unfolded to have maximal coverage of the notes in the alignment.
|
|
111
|
-
See `partitura.score.unfold_part_alignment
|
|
110
|
+
See `partitura.score.unfold_part_alignment`, defaults to True.
|
|
112
111
|
tempo_indication : str or None
|
|
113
112
|
The tempo direction indicated in the beginning of the score
|
|
114
113
|
diff_score_version_notes : list or None
|
|
@@ -211,18 +210,28 @@ def matchfile_from_alignment(
|
|
|
211
210
|
# Info for sorting lines
|
|
212
211
|
snote_sort_info = dict()
|
|
213
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
|
+
|
|
214
223
|
time_signatures = spart.iter_all(score.TimeSignature, m.start, m.end)
|
|
215
224
|
|
|
216
225
|
for tsig in time_signatures:
|
|
217
226
|
time_divs = int(tsig.start.t)
|
|
218
227
|
time_beats = float(beat_map(time_divs))
|
|
228
|
+
ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
|
|
219
229
|
dpq = int(spart.quarter_duration_map(time_divs))
|
|
230
|
+
divs_per_beat = 4 / ts_den * dpq
|
|
220
231
|
beat = int((time_beats - msb) // 1)
|
|
221
232
|
|
|
222
|
-
ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
|
|
223
|
-
|
|
224
233
|
moffset_divs = Fraction(
|
|
225
|
-
int(time_divs - msd - beat *
|
|
234
|
+
int(time_divs - msd - beat * divs_per_beat), int(ts_den * divs_per_beat)
|
|
226
235
|
)
|
|
227
236
|
|
|
228
237
|
scoreprop_lines["time_signatures"].append(
|
|
@@ -248,15 +257,15 @@ def matchfile_from_alignment(
|
|
|
248
257
|
key_signatures = spart.iter_all(score.KeySignature, m.start, m.end)
|
|
249
258
|
|
|
250
259
|
for ksig in key_signatures:
|
|
251
|
-
time_divs = int(
|
|
260
|
+
time_divs = int(ksig.start.t)
|
|
252
261
|
time_beats = float(beat_map(time_divs))
|
|
262
|
+
ts_num, ts_den, _ = spart.time_signature_map(ksig.start.t)
|
|
253
263
|
dpq = int(spart.quarter_duration_map(time_divs))
|
|
264
|
+
divs_per_beat = 4 / ts_den * dpq
|
|
254
265
|
beat = int((time_beats - msb) // 1)
|
|
255
266
|
|
|
256
|
-
ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
|
|
257
|
-
|
|
258
267
|
moffset_divs = Fraction(
|
|
259
|
-
int(time_divs - msd - beat *
|
|
268
|
+
int(time_divs - msd - beat * divs_per_beat), int(ts_den * divs_per_beat)
|
|
260
269
|
)
|
|
261
270
|
|
|
262
271
|
scoreprop_lines["key_signatures"].append(
|
|
@@ -283,27 +292,28 @@ def matchfile_from_alignment(
|
|
|
283
292
|
snotes = spart.iter_all(score.Note, m.start, m.end, include_subclasses=True)
|
|
284
293
|
# Beginning of each measure
|
|
285
294
|
for snote in snotes:
|
|
286
|
-
onset_divs
|
|
295
|
+
onset_divs = snote.start.t
|
|
296
|
+
offset_divs = snote.start.t + snote.duration_tied
|
|
287
297
|
duration_divs = offset_divs - onset_divs
|
|
288
|
-
|
|
298
|
+
# beat computations
|
|
289
299
|
onset_beats, offset_beats = beat_map([onset_divs, offset_divs])
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
beat = int((onset_beats - msb) // 1)
|
|
294
|
-
|
|
300
|
+
duration_beats = offset_beats - onset_beats
|
|
301
|
+
beat = int((onset_beats - msb) // 1) # beat field of the snote
|
|
302
|
+
# quarter, div, symbolic computation
|
|
295
303
|
ts_num, ts_den, _ = spart.time_signature_map(snote.start.t)
|
|
296
|
-
|
|
297
|
-
duration_symb = Fraction(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
moffset_divs = Fraction(
|
|
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
|
+
)
|
|
302
313
|
|
|
303
314
|
if debug:
|
|
304
|
-
duration_beats = offset_beats - onset_beats
|
|
305
315
|
moffset_beat = (onset_beats - msb - beat) / ts_den
|
|
306
|
-
assert np.isclose(float(duration_symb), duration_beats)
|
|
316
|
+
assert np.isclose(float(duration_symb), duration_beats / ts_den)
|
|
307
317
|
assert np.isclose(moffset_beat, float(moffset_divs))
|
|
308
318
|
|
|
309
319
|
score_attributes_list = []
|
|
@@ -313,6 +323,7 @@ def matchfile_from_alignment(
|
|
|
313
323
|
staff = getattr(snote, "staff", None)
|
|
314
324
|
ornaments = getattr(snote, "ornaments", None)
|
|
315
325
|
fermata = getattr(snote, "fermata", None)
|
|
326
|
+
technical = getattr(snote, "technical", None)
|
|
316
327
|
|
|
317
328
|
if voice is not None:
|
|
318
329
|
score_attributes_list.append(f"v{voice}")
|
|
@@ -329,6 +340,11 @@ def matchfile_from_alignment(
|
|
|
329
340
|
if fermata is not None:
|
|
330
341
|
score_attributes_list.append("fermata")
|
|
331
342
|
|
|
343
|
+
if technical is not None:
|
|
344
|
+
for tech_el in technical:
|
|
345
|
+
if isinstance(tech_el, score.Fingering):
|
|
346
|
+
score_attributes_list.append(f"fingering{tech_el.fingering}")
|
|
347
|
+
|
|
332
348
|
if isinstance(snote, score.GraceNote):
|
|
333
349
|
score_attributes_list.append("grace")
|
|
334
350
|
|
|
@@ -417,7 +433,7 @@ def matchfile_from_alignment(
|
|
|
417
433
|
duplicates[tuple(v)] = idx
|
|
418
434
|
voice_overlap_note_ids = []
|
|
419
435
|
if len(duplicates) > 0:
|
|
420
|
-
duplicate_idx = np.
|
|
436
|
+
duplicate_idx = np.hstack(list(duplicates.values()))
|
|
421
437
|
voice_overlap_note_ids = list(sna[duplicate_idx]["id"])
|
|
422
438
|
|
|
423
439
|
for al_note in alignment:
|
|
@@ -503,9 +519,7 @@ def matchfile_from_alignment(
|
|
|
503
519
|
|
|
504
520
|
# Concatenate all lines
|
|
505
521
|
all_match_lines += list(note_lines) + pedal_lines
|
|
506
|
-
|
|
507
522
|
matchfile = MatchFile(lines=all_match_lines)
|
|
508
|
-
|
|
509
523
|
return matchfile
|
|
510
524
|
|
|
511
525
|
|
|
@@ -6,6 +6,7 @@ This module contains methods for exporting MEI files.
|
|
|
6
6
|
import math
|
|
7
7
|
from collections import defaultdict
|
|
8
8
|
from lxml import etree
|
|
9
|
+
import lxml
|
|
9
10
|
import partitura.score as spt
|
|
10
11
|
from operator import itemgetter
|
|
11
12
|
from itertools import groupby
|
|
@@ -225,6 +226,7 @@ class MEIExporter:
|
|
|
225
226
|
self._handle_harmony(measure_el, start=measure.start.t, end=measure.end.t)
|
|
226
227
|
self._handle_fermata(measure_el, start=measure.start.t, end=measure.end.t)
|
|
227
228
|
self._handle_barline(measure_el, start=measure.start.t, end=measure.end.t)
|
|
229
|
+
self._handle_fingering(measure_el, start=measure.start.t, end=measure.end.t)
|
|
228
230
|
return measure_el
|
|
229
231
|
|
|
230
232
|
def _handle_chord(self, chord, xml_voice_el):
|
|
@@ -277,6 +279,9 @@ class MEIExporter:
|
|
|
277
279
|
elif note.tie_prev is not None:
|
|
278
280
|
note_el.set("tie", "t")
|
|
279
281
|
|
|
282
|
+
if note.stem_direction in ["up", "down"]:
|
|
283
|
+
note_el.set("stem.dir", note.stem_direction)
|
|
284
|
+
|
|
280
285
|
if note.alter is not None:
|
|
281
286
|
if (
|
|
282
287
|
note.step.lower() + ALTER_TO_MEI[note.alter]
|
|
@@ -292,7 +297,7 @@ class MEIExporter:
|
|
|
292
297
|
note_el.set("grace", "acc")
|
|
293
298
|
return duration
|
|
294
299
|
|
|
295
|
-
def _handle_tuplets(self, measure_el, start, end):
|
|
300
|
+
def _handle_tuplets(self, measure_el: lxml.etree._Element, start: int, end: int):
|
|
296
301
|
for tuplet in self.part.iter_all(spt.Tuplet, start=start, end=end):
|
|
297
302
|
start_note = tuplet.start_note
|
|
298
303
|
end_note = tuplet.end_note
|
|
@@ -349,7 +354,7 @@ class MEIExporter:
|
|
|
349
354
|
for el in xml_el_within_tuplet:
|
|
350
355
|
tuplet_el.append(el)
|
|
351
356
|
|
|
352
|
-
def _handle_beams(self, measure_el, start, end):
|
|
357
|
+
def _handle_beams(self, measure_el: lxml.etree._Element, start: int, end: int):
|
|
353
358
|
for beam in self.part.iter_all(spt.Beam, start=start, end=end):
|
|
354
359
|
# If the beam has only one note, skip it
|
|
355
360
|
if len(beam.notes) < 2:
|
|
@@ -397,7 +402,9 @@ class MEIExporter:
|
|
|
397
402
|
if note_el.getparent() != beam_el:
|
|
398
403
|
beam_el.append(note_el)
|
|
399
404
|
|
|
400
|
-
def _handle_clef_changes(
|
|
405
|
+
def _handle_clef_changes(
|
|
406
|
+
self, measure_el: lxml.etree._Element, start: int, end: int
|
|
407
|
+
):
|
|
401
408
|
for clef in self.part.iter_all(spt.Clef, start=start, end=end):
|
|
402
409
|
# Clef element is parent of the note element
|
|
403
410
|
if clef.start.t == 0:
|
|
@@ -418,7 +425,7 @@ class MEIExporter:
|
|
|
418
425
|
clef_el.set("shape", str(clef.sign))
|
|
419
426
|
clef_el.set("line", str(clef.line))
|
|
420
427
|
|
|
421
|
-
def _handle_ks_changes(self, measure_el, start, end):
|
|
428
|
+
def _handle_ks_changes(self, measure_el: lxml.etree._Element, start: int, end: int):
|
|
422
429
|
# For key signature changes, we add a new scoreDef element at the beginning of the measure
|
|
423
430
|
# and add the key signature element as attributes of the scoreDef element
|
|
424
431
|
for key_sig in self.part.iter_all(spt.KeySignature, start=start, end=end):
|
|
@@ -448,7 +455,7 @@ class MEIExporter:
|
|
|
448
455
|
parent = measure_el.getparent()
|
|
449
456
|
parent.insert(parent.index(measure_el), score_def_el)
|
|
450
457
|
|
|
451
|
-
def _handle_ts_changes(self, measure_el, start, end):
|
|
458
|
+
def _handle_ts_changes(self, measure_el: lxml.etree._Element, start: int, end: int):
|
|
452
459
|
# For key signature changes, we add a new scoreDef element at the beginning of the measure
|
|
453
460
|
# and add the key signature element as attributes of the scoreDef element
|
|
454
461
|
for time_sig in self.part.iter_all(spt.TimeSignature, start=start, end=end):
|
|
@@ -464,7 +471,7 @@ class MEIExporter:
|
|
|
464
471
|
score_def_el.set("count", str(time_sig.beats))
|
|
465
472
|
score_def_el.set("unit", str(time_sig.beat_type))
|
|
466
473
|
|
|
467
|
-
def _handle_harmony(self, measure_el, start, end):
|
|
474
|
+
def _handle_harmony(self, measure_el: lxml.etree._Element, start: int, end: int):
|
|
468
475
|
"""
|
|
469
476
|
For harmonies we add a new harm element at the beginning of the measure.
|
|
470
477
|
The position doesn't really matter since the tstamp attribute will place it correctly
|
|
@@ -505,7 +512,7 @@ class MEIExporter:
|
|
|
505
512
|
# text is a child element of harmony but not a xml element
|
|
506
513
|
harm_el.text = "|" + harmony.text
|
|
507
514
|
|
|
508
|
-
def _handle_fermata(self, measure_el, start, end):
|
|
515
|
+
def _handle_fermata(self, measure_el: lxml.etree._Element, start: int, end: int):
|
|
509
516
|
for fermata in self.part.iter_all(spt.Fermata, start=start, end=end):
|
|
510
517
|
if fermata.ref is not None:
|
|
511
518
|
note = fermata.ref
|
|
@@ -524,7 +531,7 @@ class MEIExporter:
|
|
|
524
531
|
# Set the fermata to be above the staff (the highest staff)
|
|
525
532
|
fermata_el.set("staff", "1")
|
|
526
533
|
|
|
527
|
-
def _handle_barline(self, measure_el, start, end):
|
|
534
|
+
def _handle_barline(self, measure_el: lxml.etree._Element, start: int, end: int):
|
|
528
535
|
for end_barline in self.part.iter_all(
|
|
529
536
|
spt.Ending, start=end, end=end + 1, mode="ending"
|
|
530
537
|
):
|
|
@@ -543,6 +550,26 @@ class MEIExporter:
|
|
|
543
550
|
):
|
|
544
551
|
measure_el.set("left", "rptstart")
|
|
545
552
|
|
|
553
|
+
def _handle_fingering(self, measure_el: lxml.etree._Element, start: int, end: int):
|
|
554
|
+
"""
|
|
555
|
+
For fingering we add a new fing element at the end of the measure.
|
|
556
|
+
The position doesn't really matter since the startid attribute will place it correctly
|
|
557
|
+
"""
|
|
558
|
+
for note in self.part.iter_all(spt.Note, start=start, end=end):
|
|
559
|
+
if note.technical is not None:
|
|
560
|
+
for technical_notation in note.technical:
|
|
561
|
+
if (
|
|
562
|
+
isinstance(technical_notation, score.Fingering)
|
|
563
|
+
and note.id is not None
|
|
564
|
+
):
|
|
565
|
+
fing_el = etree.SubElement(measure_el, "fing")
|
|
566
|
+
fing_el.set(XMLNS_ID, "fing-" + self.elc_id())
|
|
567
|
+
fing_el.set("startid", note.id)
|
|
568
|
+
# Naive way to place the fingering notation
|
|
569
|
+
fing_el.set("place", ("above" if note.staff == 1 else "below"))
|
|
570
|
+
# text is a child element of fingering but not a xml element
|
|
571
|
+
fing_el.text = technical_notation.fingering
|
|
572
|
+
|
|
546
573
|
|
|
547
574
|
@deprecated_alias(parts="score_data")
|
|
548
575
|
def save_mei(
|
|
@@ -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)
|
|
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)
|
|
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)
|
|
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)] =
|
|
55
|
+
channel[(pg, p, v)] = 0
|
|
56
56
|
elif mode == 4:
|
|
57
57
|
track[(pg, p, v)] = 0
|
|
58
|
-
channel[(pg, p, v)] =
|
|
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)] =
|
|
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,
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
|
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(
|