partitura 1.6.0__tar.gz → 1.8.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 (112) hide show
  1. {partitura-1.6.0 → partitura-1.8.0}/PKG-INFO +34 -19
  2. partitura-1.8.0/docs/source/conf.py +120 -0
  3. {partitura-1.6.0 → partitura-1.8.0}/partitura/__init__.py +8 -9
  4. {partitura-1.6.0 → partitura-1.8.0}/partitura/assets/score_example.musicxml +3 -0
  5. {partitura-1.6.0 → partitura-1.8.0}/partitura/directions.py +12 -2
  6. {partitura-1.6.0 → partitura-1.8.0}/partitura/display.py +4 -4
  7. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/__init__.py +1 -0
  8. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/exportaudio.py +1 -0
  9. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/exportkern.py +6 -1
  10. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/exportmatch.py +36 -26
  11. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/exportmidi.py +28 -15
  12. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/exportmusicxml.py +58 -0
  13. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importdcml.py +9 -4
  14. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importmatch.py +45 -20
  15. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importmei.py +1 -0
  16. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importmidi.py +33 -31
  17. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importmusicxml.py +104 -27
  18. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importparangonada.py +1 -0
  19. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/matchfile_base.py +1 -0
  20. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/matchfile_utils.py +1 -0
  21. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/matchlines_v0.py +1 -0
  22. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/matchlines_v1.py +1 -0
  23. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/__init__.py +0 -1
  24. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/key_identification.py +1 -0
  25. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/note_features.py +1 -0
  26. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/performance_codec.py +52 -21
  27. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/performance_features.py +24 -4
  28. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/pitch_spelling.py +1 -0
  29. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/voice_separation.py +1 -0
  30. {partitura-1.6.0 → partitura-1.8.0}/partitura/performance.py +37 -16
  31. {partitura-1.6.0 → partitura-1.8.0}/partitura/score.py +255 -92
  32. {partitura-1.6.0 → partitura-1.8.0}/partitura/utils/__init__.py +0 -1
  33. {partitura-1.6.0 → partitura-1.8.0}/partitura/utils/generic.py +55 -62
  34. {partitura-1.6.0 → partitura-1.8.0}/partitura/utils/globals.py +0 -1
  35. {partitura-1.6.0 → partitura-1.8.0}/partitura/utils/misc.py +3 -2
  36. {partitura-1.6.0 → partitura-1.8.0}/partitura/utils/music.py +323 -90
  37. {partitura-1.6.0 → partitura-1.8.0}/partitura/utils/normalize.py +1 -0
  38. {partitura-1.6.0 → partitura-1.8.0}/partitura/utils/synth.py +1 -0
  39. {partitura-1.6.0 → partitura-1.8.0}/partitura.egg-info/PKG-INFO +34 -19
  40. {partitura-1.6.0 → partitura-1.8.0}/partitura.egg-info/SOURCES.txt +3 -2
  41. partitura-1.8.0/partitura.egg-info/requires.txt +31 -0
  42. partitura-1.8.0/partitura.egg-info/top_level.txt +4 -0
  43. partitura-1.8.0/pyproject.toml +92 -0
  44. partitura-1.8.0/tests/test_harmony.py +387 -0
  45. partitura-1.8.0/tests/test_iter.py +164 -0
  46. {partitura-1.6.0 → partitura-1.8.0}/tests/test_midi_export.py +10 -0
  47. {partitura-1.6.0 → partitura-1.8.0}/tests/test_midi_import.py +136 -1
  48. {partitura-1.6.0 → partitura-1.8.0}/tests/test_note_features.py +14 -0
  49. partitura-1.8.0/tests/test_part_properties.py +103 -0
  50. {partitura-1.6.0 → partitura-1.8.0}/tests/test_performance.py +1 -1
  51. {partitura-1.6.0 → partitura-1.8.0}/tests/test_pianoroll.py +2 -1
  52. {partitura-1.6.0 → partitura-1.8.0}/tests/test_synth.py +2 -2
  53. {partitura-1.6.0 → partitura-1.8.0}/tests/test_times.py +4 -7
  54. partitura-1.8.0/tests/test_transpose.py +43 -0
  55. {partitura-1.6.0 → partitura-1.8.0}/tests/test_utils.py +65 -1
  56. {partitura-1.6.0 → partitura-1.8.0}/tests/test_xml.py +25 -12
  57. partitura-1.6.0/partitura.egg-info/requires.txt +0 -6
  58. partitura-1.6.0/partitura.egg-info/top_level.txt +0 -1
  59. partitura-1.6.0/setup.py +0 -79
  60. partitura-1.6.0/tests/test_harmony.py +0 -31
  61. partitura-1.6.0/tests/test_part_properties.py +0 -38
  62. partitura-1.6.0/tests/test_quarter_adjust.py +0 -157
  63. partitura-1.6.0/tests/test_transpose.py +0 -26
  64. {partitura-1.6.0 → partitura-1.8.0}/LICENSE +0 -0
  65. {partitura-1.6.0 → partitura-1.8.0}/README.md +0 -0
  66. {partitura-1.6.0 → partitura-1.8.0}/partitura/assets/musicxml.xsd +0 -0
  67. {partitura-1.6.0 → partitura-1.8.0}/partitura/assets/score_example.krn +0 -0
  68. {partitura-1.6.0 → partitura-1.8.0}/partitura/assets/score_example.mei +0 -0
  69. {partitura-1.6.0 → partitura-1.8.0}/partitura/assets/score_example.mid +0 -0
  70. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/exportmei.py +1 -1
  71. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/exportparangonada.py +0 -0
  72. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importkern.py +1 -1
  73. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importmusic21.py +0 -0
  74. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/importnakamura.py +1 -1
  75. {partitura-1.6.0 → partitura-1.8.0}/partitura/io/musescore.py +0 -0
  76. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/meter.py +0 -0
  77. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/note_array_to_score.py +0 -0
  78. {partitura-1.6.0 → partitura-1.8.0}/partitura/musicanalysis/tonal_tension.py +1 -1
  79. {partitura-1.6.0 → partitura-1.8.0}/partitura/utils/fluidsynth.py +0 -0
  80. {partitura-1.6.0 → partitura-1.8.0}/partitura.egg-info/dependency_links.txt +0 -0
  81. {partitura-1.6.0 → partitura-1.8.0}/setup.cfg +0 -0
  82. {partitura-1.6.0 → partitura-1.8.0}/tests/test_clef.py +0 -0
  83. {partitura-1.6.0 → partitura-1.8.0}/tests/test_cross_staff.py +0 -0
  84. {partitura-1.6.0 → partitura-1.8.0}/tests/test_dcml_import.py +0 -0
  85. {partitura-1.6.0 → partitura-1.8.0}/tests/test_deprecations.py +0 -0
  86. {partitura-1.6.0 → partitura-1.8.0}/tests/test_display.py +0 -0
  87. {partitura-1.6.0 → partitura-1.8.0}/tests/test_fluidsynth.py +0 -0
  88. {partitura-1.6.0 → partitura-1.8.0}/tests/test_kern.py +0 -0
  89. {partitura-1.6.0 → partitura-1.8.0}/tests/test_key_estimation.py +0 -0
  90. {partitura-1.6.0 → partitura-1.8.0}/tests/test_load_performance.py +0 -0
  91. {partitura-1.6.0 → partitura-1.8.0}/tests/test_load_score.py +0 -0
  92. {partitura-1.6.0 → partitura-1.8.0}/tests/test_m21_import.py +0 -0
  93. {partitura-1.6.0 → partitura-1.8.0}/tests/test_match_export.py +0 -0
  94. {partitura-1.6.0 → partitura-1.8.0}/tests/test_match_import.py +0 -0
  95. {partitura-1.6.0 → partitura-1.8.0}/tests/test_mei.py +0 -0
  96. {partitura-1.6.0 → partitura-1.8.0}/tests/test_merge_parts.py +0 -0
  97. {partitura-1.6.0 → partitura-1.8.0}/tests/test_metrical_position.py +0 -0
  98. {partitura-1.6.0 → partitura-1.8.0}/tests/test_musescore.py +0 -0
  99. {partitura-1.6.0 → partitura-1.8.0}/tests/test_nakamura.py +0 -0
  100. {partitura-1.6.0 → partitura-1.8.0}/tests/test_new_divs.py +0 -0
  101. {partitura-1.6.0 → partitura-1.8.0}/tests/test_note_array.py +0 -0
  102. {partitura-1.6.0 → partitura-1.8.0}/tests/test_octave_shift.py +0 -0
  103. {partitura-1.6.0 → partitura-1.8.0}/tests/test_parangonada.py +0 -0
  104. {partitura-1.6.0 → partitura-1.8.0}/tests/test_partial_measures.py +0 -0
  105. {partitura-1.6.0 → partitura-1.8.0}/tests/test_performance_codec.py +0 -0
  106. {partitura-1.6.0 → partitura-1.8.0}/tests/test_performance_features.py +0 -0
  107. {partitura-1.6.0 → partitura-1.8.0}/tests/test_pitch_spelling.py +0 -0
  108. {partitura-1.6.0 → partitura-1.8.0}/tests/test_rest_array.py +0 -0
  109. {partitura-1.6.0 → partitura-1.8.0}/tests/test_time_estimation.py +0 -0
  110. {partitura-1.6.0 → partitura-1.8.0}/tests/test_tonal_tension.py +0 -0
  111. {partitura-1.6.0 → partitura-1.8.0}/tests/test_urlload.py +0 -0
  112. {partitura-1.6.0 → partitura-1.8.0}/tests/test_voice_estimation.py +0 -0
@@ -1,18 +1,21 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: partitura
3
- Version: 1.6.0
3
+ Version: 1.8.0
4
4
  Summary: A package for handling symbolic musical information
5
- Home-page: https://github.com/CPJKU/partitura
6
5
  Author: Maarten Grachten, Carlos Cancino-Chacón, Silvan Peter, Emmanouil Karystinaios, Francesco Foscarin, Thassilo Gadermaier, Patricia Hu
7
- Author-email: partitura-users@googlegroups.com
6
+ Maintainer-email: partitura-users@googlegroups.com
8
7
  License: Apache 2.0
9
- Keywords: music notation musicxml midi
10
- Classifier: License :: OSI Approved :: MIT License
8
+ Project-URL: Homepage, https://github.com/CPJKU/partitura
9
+ Keywords: music,notation,musicxml,midi
10
+ Classifier: License :: OSI Approved :: Apache Software License
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
13
16
  Classifier: Programming Language :: Python :: Implementation :: CPython
14
17
  Classifier: Programming Language :: Python :: Implementation :: PyPy
15
- Requires-Python: >=3.7
18
+ Requires-Python: >=3.10
16
19
  Description-Content-Type: text/markdown
17
20
  License-File: LICENSE
18
21
  Requires-Dist: numpy
@@ -21,18 +24,30 @@ Requires-Dist: lxml
21
24
  Requires-Dist: lark-parser
22
25
  Requires-Dist: xmlschema
23
26
  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: requires-dist
33
- Dynamic: requires-python
34
- Dynamic: summary
35
-
27
+ Requires-Dist: typing-extensions
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: black; extra == "dev"
31
+ Requires-Dist: nbsphinx; extra == "dev"
32
+ Requires-Dist: music21; extra == "dev"
33
+ Requires-Dist: Pillow; extra == "dev"
34
+ Requires-Dist: musescore; extra == "dev"
35
+ Requires-Dist: miditok; extra == "dev"
36
+ Requires-Dist: tokenizers; extra == "dev"
37
+ Requires-Dist: pandas; extra == "dev"
38
+ Provides-Extra: all
39
+ Requires-Dist: nbsphinx; extra == "all"
40
+ Requires-Dist: torch; extra == "all"
41
+ Requires-Dist: pyfluidsynth; extra == "all"
42
+ Requires-Dist: pytest; extra == "all"
43
+ Requires-Dist: black; extra == "all"
44
+ Requires-Dist: music21; extra == "all"
45
+ Requires-Dist: Pillow; extra == "all"
46
+ Requires-Dist: musescore; extra == "all"
47
+ Requires-Dist: miditok; extra == "all"
48
+ Requires-Dist: tokenizers; extra == "all"
49
+ Requires-Dist: pandas; extra == "all"
50
+ Dynamic: license-file
36
51
 
37
52
  [//]: # (<p align="center"> )
38
53
 
@@ -0,0 +1,120 @@
1
+ # Configuration file for the Sphinx documentation builder.
2
+ #
3
+ # This file only contains a selection of the most common options. For a full
4
+ # list see the documentation:
5
+ # http://www.sphinx-doc.org/en/master/config
6
+
7
+ # -- Path setup --------------------------------------------------------------
8
+
9
+ # If extensions (or modules to document with autodoc) are in another directory,
10
+ # add these directories to sys.path here. If the directory is relative to the
11
+ # documentation root, use os.path.abspath to make it absolute, like shown here.
12
+ #
13
+ import os
14
+ import sys
15
+
16
+ sys.path.insert(0, os.path.abspath("../../partitura"))
17
+ # The master toctree document.
18
+ master_doc = "index"
19
+
20
+ # -- Project information -----------------------------------------------------
21
+
22
+ project = "partitura"
23
+ # copyright = '2019, Maarten Grachten'
24
+ author = "Maarten Grachten, Carlos Cancino-Chacón, Silvan Peter, Emmanouil Karystinaios, Francesco Foscarin, Thassilo Gadermaier"
25
+
26
+ # The version info for the project you're documenting, acts as replacement for
27
+ # |version| and |release|, also used in various other places throughout the
28
+ # built documents.
29
+ #
30
+ # The short X.Y version.
31
+ version = "1.8.0"
32
+ # The full version, including alpha/beta/rc tags.
33
+ release = "1.8.0"
34
+
35
+ # The language for content autogenerated by Sphinx. Refer to documentation
36
+ # for a list of supported languages.
37
+ #
38
+ # This is also used if you do content translation via gettext catalogs.
39
+ # Usually you set "language" from the command line for these cases.
40
+ language = "python"
41
+
42
+
43
+ # -- General configuration ---------------------------------------------------
44
+
45
+ # List of patterns, relative to source directory, that match files and
46
+ # directories to ignore when looking for source files.
47
+ exclude_patterns = ["_build"]
48
+
49
+ # The name of the Pygments (syntax highlighting) style to use.
50
+ pygments_style = "sphinx"
51
+
52
+ # Add any Sphinx extension module names here, as strings. They can be
53
+ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
54
+ # ones.
55
+ extensions = [
56
+ "sphinx.ext.autodoc",
57
+ "sphinx.ext.autosummary",
58
+ "sphinx.ext.doctest",
59
+ "sphinx.ext.todo",
60
+ "sphinx.ext.coverage",
61
+ "sphinx.ext.mathjax",
62
+ "sphinx.ext.viewcode",
63
+ # 'sphinxcontrib.napoleon',
64
+ "sphinx.ext.napoleon",
65
+ "nbsphinx",
66
+ # 'sphinxcontrib.bibtex', # for bibliographic references
67
+ # 'sphinxcontrib.rsvgconverter', # for SVG->PDF conversion in LaTeX output
68
+ # 'sphinx_gallery.load_style', # load CSS for gallery (needs SG >= 0.6)
69
+ # 'sphinx_last_updated_by_git', #? get "last updated" from Git
70
+ # 'sphinx_codeautolink', # automatic links from code to documentation
71
+ # 'sphinx.ext.intersphinx', # links to other Sphinx projects (e.g. NumPy)
72
+ ]
73
+
74
+ # These projects are also used for the sphinx_codeautolink extension:
75
+ intersphinx_mapping = {
76
+ "IPython": ("https://ipython.readthedocs.io/en/stable/", None),
77
+ "matplotlib": ("https://matplotlib.org/", None),
78
+ "numpy": ("https://docs.scipy.org/doc/numpy/", None),
79
+ "pandas": ("https://pandas.pydata.org/docs/", None),
80
+ "python": ("https://docs.python.org/3/", None),
81
+ }
82
+
83
+ # see http://stackoverflow.com/q/12206334/562769
84
+ numpydoc_show_class_members = False
85
+
86
+ # Add any paths that contain templates here, relative to this directory.
87
+ templates_path = ["_templates"]
88
+
89
+ # The suffix(es) of source filenames.
90
+ # You can specify multiple suffix as a list of string:
91
+ # source_suffix = ['.rst', '.md']
92
+ source_suffix = ".rst"
93
+
94
+ # List of patterns, relative to source directory, that match files and
95
+ # directories to ignore when looking for source files.
96
+ # This pattern also affects html_static_path and html_extra_path.
97
+ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
98
+
99
+
100
+ # -- Options for HTML output -------------------------------------------------
101
+
102
+ # The theme to use for HTML and HTML Help pages. See the documentation for
103
+ # a list of builtin themes.
104
+ #
105
+ # only import and set the theme if we're building docs locally
106
+ if os.environ.get("READTHEDOCS") != "True":
107
+ import sphinx_rtd_theme
108
+
109
+ html_theme = "sphinx_rtd_theme"
110
+ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
111
+ else:
112
+ html_theme = "default"
113
+
114
+ # Add any paths that contain custom static files (such as style sheets) here,
115
+ # relative to this directory. They are copied after the builtin static files,
116
+ # so a file named "default.css" will overwrite the builtin "default.css".
117
+ # html_static_path = ['_static']
118
+
119
+ # Output file base name for HTML help builder.
120
+ htmlhelp_basename = "partituradoc"
@@ -6,7 +6,9 @@ data, display rendered scores, and functions to estimate pitch
6
6
  spelling, voice assignment, and key signature.
7
7
  """
8
8
 
9
- import pkg_resources
9
+ import sys
10
+ from importlib.metadata import version
11
+ from importlib.resources import files
10
12
 
11
13
  from .io import load_score, load_performance, load_score_as_part, lp
12
14
  from .io.musescore import load_via_musescore
@@ -29,18 +31,15 @@ from .display import render
29
31
  from . import musicanalysis
30
32
  from .musicanalysis import make_note_features, compute_note_array, full_note_array
31
33
 
32
-
33
34
  # define a version variable
34
- __version__ = pkg_resources.get_distribution("partitura").version
35
+ __version__ = version("partitura")
35
36
 
36
37
  #: An example MusicXML file for didactic purposes
37
- EXAMPLE_MUSICXML = pkg_resources.resource_filename(
38
- "partitura", "assets/score_example.musicxml"
39
- )
38
+ EXAMPLE_MUSICXML = str(files("partitura") / "assets" / "score_example.musicxml")
40
39
 
41
- EXAMPLE_MIDI = pkg_resources.resource_filename("partitura", "assets/score_example.mid")
42
- EXAMPLE_MEI = pkg_resources.resource_filename("partitura", "assets/score_example.mei")
43
- EXAMPLE_KERN = pkg_resources.resource_filename("partitura", "assets/score_example.krn")
40
+ EXAMPLE_MIDI = str(files("partitura") / "assets" / "score_example.mid")
41
+ EXAMPLE_MEI = str(files("partitura") / "assets" / "score_example.mei")
42
+ EXAMPLE_KERN = str(files("partitura") / "assets" / "score_example.krn")
44
43
 
45
44
  __all__ = [
46
45
  "load_score",
@@ -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>
@@ -327,6 +327,12 @@ def regularize_form(children):
327
327
  return " ".join(unabbreviate(ch.lower()) for ch in children)
328
328
 
329
329
 
330
+ def check_tree(tree, col_name="column"):
331
+ meta = getattr(tree, "meta", None)
332
+ col = getattr(meta, col_name, None)
333
+ return col
334
+
335
+
330
336
  def create_directions(tree, string, start=None, end=None):
331
337
  """
332
338
  Recursively walk the parse tree of `string` to create a `score.Direction`
@@ -334,9 +340,13 @@ def create_directions(tree, string, start=None, end=None):
334
340
 
335
341
  """
336
342
  if start is None:
337
- start = tree.column - 1
343
+ col = check_tree(tree, "column")
344
+ if col is not None:
345
+ start = col - 1
338
346
  if end is None:
339
- end = tree.end_column - 1
347
+ col = check_tree(tree, "end_column")
348
+ if col is not None:
349
+ end = col - 1
340
350
 
341
351
  if tree.data == "conj":
342
352
  return create_directions(tree.children[0], string) + create_directions(
@@ -20,7 +20,6 @@ from partitura.score import ScoreLike
20
20
 
21
21
  from partitura.utils.misc import PathLike, deprecated_alias
22
22
 
23
-
24
23
  __all__ = ["render"]
25
24
 
26
25
  # def ly_install_msg():
@@ -117,9 +116,10 @@ def render_lilypond(
117
116
 
118
117
  prvw_sfx = ".preview.{}".format(fmt)
119
118
 
120
- with TemporaryFile() as xml_fh, NamedTemporaryFile(
121
- suffix=prvw_sfx, delete=False
122
- ) as img_fh:
119
+ with (
120
+ TemporaryFile() as xml_fh,
121
+ NamedTemporaryFile(suffix=prvw_sfx, delete=False) as img_fh,
122
+ ):
123
123
  # save part to musicxml in file handle xml_fh
124
124
  save_musicxml(score_data, xml_fh)
125
125
  # rewind read pointer of file handle before we pass it to musicxml2ly
@@ -3,6 +3,7 @@
3
3
  """
4
4
  This module contains methods for importing and exporting symbolic music formats.
5
5
  """
6
+
6
7
  from typing import Union
7
8
  import os
8
9
  import urllib.request
@@ -3,6 +3,7 @@
3
3
  """
4
4
  This module contains methods to synthesize a Partitura ScoreLike object to wav.
5
5
  """
6
+
6
7
  from typing import Union, Optional, Callable, Dict, Any
7
8
  import numpy as np
8
9
  from scipy.io import wavfile
@@ -3,8 +3,10 @@
3
3
  """
4
4
  This module contains methods for exporting Kern files.
5
5
  """
6
+
6
7
  import math
7
8
  from collections import defaultdict
9
+ from importlib.metadata import version
8
10
 
9
11
  import numpy
10
12
 
@@ -19,6 +21,9 @@ from partitura.utils.misc import deprecated_alias, PathLike
19
21
  __all__ = ["save_kern"]
20
22
 
21
23
 
24
+ VERSION = version("partitura")
25
+
26
+
22
27
  ACC_TO_SIGN = {
23
28
  0: "n",
24
29
  -1: "-",
@@ -327,7 +332,7 @@ def save_kern(
327
332
  out_data = exporter.parse()
328
333
  out_data = exporter.trim(out_data)
329
334
  # Use numpy savetxt to save the file
330
- footer = "Encoded using the Partitura Python package, version 1.6.0"
335
+ footer = f"Encoded using the Partitura Python package, version {VERSION}"
331
336
  if out is not None:
332
337
  np.savetxt(
333
338
  fname=out,
@@ -7,6 +7,7 @@ Notes
7
7
  -----
8
8
  * The methods only export matchfiles version 1.0.0.
9
9
  """
10
+
10
11
  import numpy as np
11
12
 
12
13
  from typing import List, Optional, Iterable
@@ -70,7 +71,7 @@ def matchfile_from_alignment(
70
71
  piece: Optional[str] = None,
71
72
  score_filename: Optional[PathLike] = None,
72
73
  performance_filename: Optional[PathLike] = None,
73
- assume_part_unfolded: bool = False,
74
+ assume_part_unfolded: bool = True,
74
75
  tempo_indication: Optional[str] = None,
75
76
  diff_score_version_notes: Optional[list] = None,
76
77
  version: Version = LATEST_VERSION,
@@ -107,7 +108,7 @@ def matchfile_from_alignment(
107
108
  Whether to assume that the part has been unfolded according to the
108
109
  repetitions in the alignment. If False, the part will be automatically
109
110
  unfolded to have maximal coverage of the notes in the alignment.
110
- See `partitura.score.unfold_part_alignment`.
111
+ See `partitura.score.unfold_part_alignment`, defaults to True.
111
112
  tempo_indication : str or None
112
113
  The tempo direction indicated in the beginning of the score
113
114
  diff_score_version_notes : list or None
@@ -210,18 +211,28 @@ def matchfile_from_alignment(
210
211
  # Info for sorting lines
211
212
  snote_sort_info = dict()
212
213
  for (mnum, msd, msb), m in zip(measure_starts, measures):
214
+ if mnum == 0:
215
+ # handle offsets in anacrusis measure
216
+ ts_num, ts_den, _ = spart.time_signature_map(0)
217
+ dpq = int(spart.quarter_duration_map(0))
218
+ measure_dur_in_divs = m.end.t - m.start.t
219
+ expected_measure_dur = ts_num * 4 / ts_den * dpq
220
+ if measure_dur_in_divs < expected_measure_dur:
221
+ msd -= expected_measure_dur - measure_dur_in_divs
222
+ msb -= (expected_measure_dur - measure_dur_in_divs) / dpq * ts_den / 4
223
+
213
224
  time_signatures = spart.iter_all(score.TimeSignature, m.start, m.end)
214
225
 
215
226
  for tsig in time_signatures:
216
227
  time_divs = int(tsig.start.t)
217
228
  time_beats = float(beat_map(time_divs))
229
+ ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
218
230
  dpq = int(spart.quarter_duration_map(time_divs))
231
+ divs_per_beat = 4 / ts_den * dpq
219
232
  beat = int((time_beats - msb) // 1)
220
233
 
221
- ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
222
-
223
234
  moffset_divs = Fraction(
224
- int(time_divs - msd - beat * dpq), (int(ts_den) * dpq)
235
+ int(time_divs - msd - beat * divs_per_beat), int(ts_den * divs_per_beat)
225
236
  )
226
237
 
227
238
  scoreprop_lines["time_signatures"].append(
@@ -247,15 +258,15 @@ def matchfile_from_alignment(
247
258
  key_signatures = spart.iter_all(score.KeySignature, m.start, m.end)
248
259
 
249
260
  for ksig in key_signatures:
250
- time_divs = int(tsig.start.t)
261
+ time_divs = int(ksig.start.t)
251
262
  time_beats = float(beat_map(time_divs))
263
+ ts_num, ts_den, _ = spart.time_signature_map(ksig.start.t)
252
264
  dpq = int(spart.quarter_duration_map(time_divs))
265
+ divs_per_beat = 4 / ts_den * dpq
253
266
  beat = int((time_beats - msb) // 1)
254
267
 
255
- ts_num, ts_den, _ = spart.time_signature_map(tsig.start.t)
256
-
257
268
  moffset_divs = Fraction(
258
- int(time_divs - msd - beat * dpq), (int(ts_den) * dpq)
269
+ int(time_divs - msd - beat * divs_per_beat), int(ts_den * divs_per_beat)
259
270
  )
260
271
 
261
272
  scoreprop_lines["key_signatures"].append(
@@ -282,27 +293,28 @@ def matchfile_from_alignment(
282
293
  snotes = spart.iter_all(score.Note, m.start, m.end, include_subclasses=True)
283
294
  # Beginning of each measure
284
295
  for snote in snotes:
285
- onset_divs, offset_divs = snote.start.t, snote.start.t + snote.duration_tied
296
+ onset_divs = snote.start.t
297
+ offset_divs = snote.start.t + snote.duration_tied
286
298
  duration_divs = offset_divs - onset_divs
287
-
299
+ # beat computations
288
300
  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
-
301
+ duration_beats = offset_beats - onset_beats
302
+ beat = int((onset_beats - msb) // 1) # beat field of the snote
303
+ # quarter, div, symbolic computation
294
304
  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))
305
+ dpq = int(spart.quarter_duration_map(onset_divs))
306
+ duration_symb = Fraction(
307
+ duration_divs, dpq * 4
308
+ ) # compute duration from quarters/divs
309
+ divs_per_beat = 4 / ts_den * dpq
310
+ moffset_divs = Fraction(
311
+ int(onset_divs - msd - beat * divs_per_beat),
312
+ int(ts_den * divs_per_beat),
313
+ )
301
314
 
302
315
  if debug:
303
- duration_beats = offset_beats - onset_beats
304
316
  moffset_beat = (onset_beats - msb - beat) / ts_den
305
- assert np.isclose(float(duration_symb), duration_beats)
317
+ assert np.isclose(float(duration_symb), duration_beats / ts_den)
306
318
  assert np.isclose(moffset_beat, float(moffset_divs))
307
319
 
308
320
  score_attributes_list = []
@@ -508,9 +520,7 @@ def matchfile_from_alignment(
508
520
 
509
521
  # Concatenate all lines
510
522
  all_match_lines += list(note_lines) + pedal_lines
511
-
512
523
  matchfile = MatchFile(lines=all_match_lines)
513
-
514
524
  return matchfile
515
525
 
516
526
 
@@ -3,6 +3,7 @@
3
3
  """
4
4
  This module contains methods for exporting MIDI files
5
5
  """
6
+
6
7
  import numpy as np
7
8
 
8
9
  from collections import defaultdict, OrderedDict
@@ -36,34 +37,34 @@ def map_to_track_channel(note_keys, mode):
36
37
  if mode == 0:
37
38
  trk = tr_helper.setdefault(p, len(tr_helper))
38
39
  ch1 = ch_helper.setdefault(p, {})
39
- ch2 = ch1.setdefault(v, len(ch1) + 1)
40
+ ch2 = ch1.setdefault(v, len(ch1))
40
41
  track[(pg, p, v)] = trk
41
42
  channel[(pg, p, v)] = ch2
42
43
  elif mode == 1:
43
44
  trk = tr_helper.setdefault(pg, len(tr_helper))
44
45
  ch1 = ch_helper.setdefault(pg, {})
45
- ch2 = ch1.setdefault(p, len(ch1) + 1)
46
+ ch2 = ch1.setdefault(p, len(ch1))
46
47
  track[(pg, p, v)] = trk
47
48
  channel[(pg, p, v)] = ch2
48
49
  elif mode == 2:
49
50
  track[(pg, p, v)] = 0
50
- ch = ch_helper.setdefault(p, len(ch_helper) + 1)
51
+ ch = ch_helper.setdefault(p, len(ch_helper))
51
52
  channel[(pg, p, v)] = ch
52
53
  elif mode == 3:
53
54
  trk = tr_helper.setdefault(p, len(tr_helper))
54
55
  track[(pg, p, v)] = trk
55
- channel[(pg, p, v)] = 1
56
+ channel[(pg, p, v)] = 0
56
57
  elif mode == 4:
57
58
  track[(pg, p, v)] = 0
58
- channel[(pg, p, v)] = 1
59
+ channel[(pg, p, v)] = 0
59
60
  elif mode == 5:
60
61
  trk = tr_helper.setdefault((p, v), len(tr_helper))
61
62
  track[(pg, p, v)] = trk
62
- channel[(pg, p, v)] = 1
63
+ channel[(pg, p, v)] = 0
63
64
  else:
64
65
  raise Exception("unsupported part/voice assign mode {}".format(mode))
65
66
 
66
- result = dict((k, (track.get(k, 0), channel.get(k, 1))) for k in note_keys)
67
+ result = dict((k, (track.get(k, 0), channel.get(k, 0))) for k in note_keys)
67
68
  # for (pg, p, voice), v in result.items():
68
69
  # pgn = pg.group_name if hasattr(pg, 'group_name') else pg.id
69
70
  # print(pgn, p.id, voice)
@@ -177,7 +178,7 @@ def save_performance_midi(
177
178
 
178
179
  for c in performed_part.controls:
179
180
  track = c.get("track", 0)
180
- ch = c.get("channel", 1)
181
+ ch = c.get("channel", 0)
181
182
  t = int(np.round(10**6 * ppq * c["time"] / mpq))
182
183
  track_events[track][t].append(
183
184
  Message(
@@ -190,7 +191,7 @@ def save_performance_midi(
190
191
 
191
192
  for n in performed_part.notes:
192
193
  track = n.get("track", 0)
193
- ch = n.get("channel", 1)
194
+ ch = n.get("channel", 0)
194
195
  t_on = int(np.round(10**6 * ppq * n["note_on"] / mpq))
195
196
  t_off = int(np.round(10**6 * ppq * n["note_off"] / mpq))
196
197
  vel = n.get("velocity", default_velocity)
@@ -203,7 +204,7 @@ def save_performance_midi(
203
204
 
204
205
  for p in performed_part.programs:
205
206
  track = p.get("track", 0)
206
- ch = p.get("channel", 1)
207
+ ch = p.get("channel", 0)
207
208
  t = int(np.round(10**6 * ppq * p["time"] / mpq))
208
209
  track_events[track][t].append(
209
210
  Message("program_change", program=int(p["program"]), channel=ch)
@@ -215,11 +216,11 @@ def save_performance_midi(
215
216
  list(
216
217
  set(
217
218
  [
218
- (c.get("channel", 1), c.get("track", 0))
219
+ (c.get("channel", 0), c.get("track", 0))
219
220
  for c in performed_part.controls
220
221
  ]
221
222
  + [
222
- (n.get("channel", 1), n.get("track", 0))
223
+ (n.get("channel", 0), n.get("track", 0))
223
224
  for n in performed_part.notes
224
225
  ]
225
226
  )
@@ -395,7 +396,7 @@ def save_score_midi(
395
396
 
396
397
  def to_ppq(t):
397
398
  # convert div times to new ppq
398
- return int(ppq * (qm(t) - ftp))
399
+ return round(ppq * (qm(t) - ftp))
399
400
 
400
401
  for tp in part.iter_all(score.Tempo):
401
402
  tempos[to_ppq(tp.start.t)] = MetaMessage(
@@ -405,6 +406,12 @@ def save_score_midi(
405
406
  if not tempos:
406
407
  tempos[0] = MetaMessage("set_tempo", tempo=500000)
407
408
 
409
+ # note velocities
410
+ velocities = []
411
+ for vp in part.iter_all(score.Dynamic):
412
+ velocities.append((vp.start.t, round(90 * vp.velocity / 100)))
413
+ velocities = np.stack(velocities) if velocities else velocities
414
+
408
415
  if anacrusis_behavior == "time_sig_change":
409
416
  # Change time signature to match the duration of the measure
410
417
  # This ensure the beat and downbeats position are coherent
@@ -486,11 +493,17 @@ def save_score_midi(
486
493
  # key is a tuple (part_group, part, voice) that will be
487
494
  # converted into a (track, channel) pair.
488
495
  key = (pg, part, note.voice)
496
+ vel = velocity
497
+ if len(velocities) > 0:
498
+ vel_idx = (
499
+ np.searchsorted(velocities[:, 0], note.start.t, side="right") - 1
500
+ )
501
+ vel = int(velocities[vel_idx, 1])
489
502
  events[key][to_ppq(note.start.t)].append(
490
- Message("note_on", note=note.midi_pitch)
503
+ Message("note_on", note=note.midi_pitch, velocity=vel)
491
504
  )
492
505
  events[key][to_ppq(note.start.t + note.duration_tied)].append(
493
- Message("note_off", note=note.midi_pitch)
506
+ Message("note_off", note=note.midi_pitch, velocity=vel)
494
507
  )
495
508
  event_keys[key] = True
496
509