partitura 1.4.1__tar.gz → 1.5.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.4.1 → partitura-1.5.0}/PKG-INFO +1 -1
- {partitura-1.4.1 → partitura-1.5.0}/partitura/__init__.py +6 -1
- {partitura-1.4.1 → partitura-1.5.0}/partitura/directions.py +1 -23
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/__init__.py +3 -1
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/exportaudio.py +79 -4
- partitura-1.5.0/partitura/io/exportkern.py +344 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/exportmatch.py +1 -1
- partitura-1.5.0/partitura/io/exportmei.py +618 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/exportmidi.py +41 -1
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/exportmusicxml.py +11 -0
- partitura-1.5.0/partitura/io/importdcml.py +353 -0
- partitura-1.5.0/partitura/io/importkern.py +797 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/importmatch.py +1 -1
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/importmidi.py +120 -17
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/importmusic21.py +15 -9
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/importmusicxml.py +54 -4
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/matchlines_v1.py +8 -6
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/key_identification.py +10 -67
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/meter.py +14 -16
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/performance_codec.py +3 -2
- {partitura-1.4.1 → partitura-1.5.0}/partitura/performance.py +6 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/score.py +1025 -20
- partitura-1.5.0/partitura/utils/fluidsynth.py +326 -0
- partitura-1.5.0/partitura/utils/globals.py +545 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/utils/misc.py +26 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/utils/music.py +222 -312
- {partitura-1.4.1 → partitura-1.5.0}/partitura/utils/normalize.py +1 -3
- {partitura-1.4.1 → partitura-1.5.0}/partitura/utils/synth.py +18 -38
- {partitura-1.4.1 → partitura-1.5.0}/partitura.egg-info/PKG-INFO +1 -1
- {partitura-1.4.1 → partitura-1.5.0}/partitura.egg-info/SOURCES.txt +6 -0
- {partitura-1.4.1 → partitura-1.5.0}/setup.py +1 -1
- partitura-1.5.0/tests/test_dcml_import.py +20 -0
- partitura-1.5.0/tests/test_fluidsynth.py +97 -0
- partitura-1.5.0/tests/test_kern.py +84 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_mei.py +38 -4
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_note_array.py +3 -5
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_synth.py +1 -1
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_utils.py +45 -45
- partitura-1.4.1/partitura/io/exportmei.py +0 -2096
- partitura-1.4.1/partitura/io/importkern.py +0 -626
- partitura-1.4.1/tests/test_kern.py +0 -48
- {partitura-1.4.1 → partitura-1.5.0}/LICENSE +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/README.md +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/assets/musicxml.xsd +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/assets/score_example.krn +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/assets/score_example.mei +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/assets/score_example.mid +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/assets/score_example.musicxml +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/display.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/exportparangonada.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/importmei.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/importnakamura.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/importparangonada.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/matchfile_base.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/matchfile_utils.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/matchlines_v0.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/io/musescore.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/__init__.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/note_array_to_score.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/note_features.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/performance_features.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/pitch_spelling.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/tonal_tension.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/musicanalysis/voice_separation.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/utils/__init__.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura/utils/generic.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura.egg-info/dependency_links.txt +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura.egg-info/requires.txt +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/partitura.egg-info/top_level.txt +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/setup.cfg +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_cross_staff_beaming.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_deprecations.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_display.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_harmony.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_key_estimation.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_load_performance.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_load_score.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_m21_import.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_match_export.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_match_import.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_merge_parts.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_metrical_position.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_midi_export.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_midi_import.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_musescore.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_nakamura.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_new_divs.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_note_features.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_octave_shift.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_parangonada.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_part_properties.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_partial_measures.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_performance.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_performance_codec.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_performance_features.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_pianoroll.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_pitch_spelling.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_quarter_adjust.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_rest_array.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_time_estimation.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_times.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_tonal_tension.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_transpose.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_voice_estimation.py +0 -0
- {partitura-1.4.1 → partitura-1.5.0}/tests/test_xml.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: partitura
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.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
|
|
@@ -15,6 +15,7 @@ from .io.exportmusicxml import save_musicxml
|
|
|
15
15
|
from .io.importmei import load_mei
|
|
16
16
|
from .io.importkern import load_kern
|
|
17
17
|
from .io.importmusic21 import load_music21
|
|
18
|
+
from .io.importdcml import load_dcml
|
|
18
19
|
from .io.importmidi import load_score_midi, load_performance_midi, midi_to_notearray
|
|
19
20
|
from .io.exportmidi import save_score_midi, save_performance_midi
|
|
20
21
|
from .io.importmatch import load_match
|
|
@@ -22,7 +23,8 @@ from .io.exportmatch import save_match
|
|
|
22
23
|
from .io.importnakamura import load_nakamuramatch, load_nakamuracorresp
|
|
23
24
|
from .io.importparangonada import load_parangonada_csv
|
|
24
25
|
from .io.exportparangonada import save_parangonada_csv, save_csv_for_parangonada
|
|
25
|
-
from .io.exportaudio import save_wav
|
|
26
|
+
from .io.exportaudio import save_wav, save_wav_fluidsynth
|
|
27
|
+
from .io.exportmei import save_mei
|
|
26
28
|
from .display import render
|
|
27
29
|
from . import musicanalysis
|
|
28
30
|
from .musicanalysis import make_note_features, compute_note_array, full_note_array
|
|
@@ -56,9 +58,12 @@ __all__ = [
|
|
|
56
58
|
"save_performance_midi",
|
|
57
59
|
"load_match",
|
|
58
60
|
"save_match",
|
|
61
|
+
"load_dcml",
|
|
59
62
|
"load_nakamuramatch",
|
|
60
63
|
"load_nakamuracorresp",
|
|
61
64
|
"load_parangonada_csv",
|
|
62
65
|
"save_parangonada_csv",
|
|
66
|
+
"save_wav",
|
|
67
|
+
"save_wav_fluidsynth",
|
|
63
68
|
"render",
|
|
64
69
|
]
|
|
@@ -13,6 +13,7 @@ The functionality is provided by the function `parse_words`
|
|
|
13
13
|
|
|
14
14
|
import re
|
|
15
15
|
import warnings
|
|
16
|
+
from partitura.utils.globals import UNABBREVS
|
|
16
17
|
|
|
17
18
|
try:
|
|
18
19
|
from lark import Lark
|
|
@@ -51,29 +52,6 @@ def join_items(items):
|
|
|
51
52
|
)
|
|
52
53
|
|
|
53
54
|
|
|
54
|
-
UNABBREVS = [
|
|
55
|
-
(re.compile(r"(crescendo|cresc\.?)"), "crescendo"),
|
|
56
|
-
(re.compile(r"(smorzando|smorz\.?)"), "smorzando"),
|
|
57
|
-
(re.compile(r"(decrescendo|(decresc|decr|dimin|dim)\.?)"), "diminuendo"),
|
|
58
|
-
(re.compile(r"((acceler|accel|acc)\.?)"), "accelerando"),
|
|
59
|
-
(re.compile(r"(ritenente|riten\.?)"), "ritenuto"),
|
|
60
|
-
(re.compile(r"((ritard|rit)\.?)"), "ritardando"),
|
|
61
|
-
(re.compile(r"((rallent|rall)\.?)"), "rallentando"),
|
|
62
|
-
(re.compile(r"(dolciss\.?)"), "dolcissimo"),
|
|
63
|
-
(re.compile(r"((sosten|sost)\.?)"), "sostenuto"),
|
|
64
|
-
(re.compile(r"(delicatiss\.?)"), "delicatissimo"),
|
|
65
|
-
(re.compile(r"(leggieramente|leggiermente|leggiero|legg\.?)"), "leggiero"),
|
|
66
|
-
(re.compile(r"(leggierissimo|(leggieriss\.?))"), "leggierissimo"),
|
|
67
|
-
(re.compile(r"(scherz\.?)"), "scherzando"),
|
|
68
|
-
(re.compile(r"(tenute|ten\.?)"), "tenuto"),
|
|
69
|
-
(re.compile(r"(allegretto)"), "allegro"),
|
|
70
|
-
(re.compile(r"(espress\.?)"), "espressivo"),
|
|
71
|
-
(re.compile(r"(ligato)"), "legato"),
|
|
72
|
-
(re.compile(r"(ligatissimo)"), "legatissimo"),
|
|
73
|
-
(re.compile(r"((rinforz|rinf|rfz|rf)\.?)"), "rinforzando"),
|
|
74
|
-
]
|
|
75
|
-
|
|
76
|
-
|
|
77
55
|
def unabbreviate(s):
|
|
78
56
|
for p, v in UNABBREVS:
|
|
79
57
|
if p.match(s):
|
|
@@ -12,10 +12,12 @@ from .musescore import load_via_musescore
|
|
|
12
12
|
from .importmatch import load_match
|
|
13
13
|
from .importmei import load_mei
|
|
14
14
|
from .importkern import load_kern
|
|
15
|
+
from .exportkern import save_kern
|
|
15
16
|
from .importparangonada import load_parangonada_csv
|
|
16
17
|
from .exportparangonada import save_parangonada_csv
|
|
17
18
|
from .importmusic21 import load_music21
|
|
18
|
-
|
|
19
|
+
from .exportmei import save_mei
|
|
20
|
+
from .importdcml import load_dcml
|
|
19
21
|
from partitura.utils.misc import (
|
|
20
22
|
deprecated_alias,
|
|
21
23
|
deprecated_parameter,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/python
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""
|
|
4
|
-
This module contains methods to synthesize Partitura object to wav
|
|
5
|
-
additive synthesis
|
|
4
|
+
This module contains methods to synthesize a Partitura ScoreLike object to wav.
|
|
6
5
|
"""
|
|
7
6
|
from typing import Union, Optional, Callable, Dict, Any
|
|
8
7
|
import numpy as np
|
|
@@ -13,10 +12,18 @@ from partitura.score import ScoreLike
|
|
|
13
12
|
from partitura.performance import PerformanceLike
|
|
14
13
|
|
|
15
14
|
from partitura.utils.synth import synthesize, SAMPLE_RATE, A4
|
|
15
|
+
from partitura.utils.fluidsynth import (
|
|
16
|
+
synthesize_fluidsynth,
|
|
17
|
+
DEFAULT_SOUNDFONT,
|
|
18
|
+
HAS_FLUIDSYNTH,
|
|
19
|
+
)
|
|
16
20
|
|
|
17
21
|
from partitura.utils.misc import PathLike
|
|
18
22
|
|
|
19
|
-
__all__ = [
|
|
23
|
+
__all__ = [
|
|
24
|
+
"save_wav",
|
|
25
|
+
"save_wav_fluidsynth",
|
|
26
|
+
]
|
|
20
27
|
|
|
21
28
|
|
|
22
29
|
def save_wav(
|
|
@@ -89,8 +96,76 @@ def save_wav(
|
|
|
89
96
|
bpm=bpm,
|
|
90
97
|
)
|
|
91
98
|
|
|
99
|
+
if out is not None:
|
|
100
|
+
# convert to 16bit integers (save as PCM 16 bit)
|
|
101
|
+
# (some DAWs cannot load audio files that are float64,
|
|
102
|
+
# e.g., Logic)
|
|
103
|
+
amplitude = np.iinfo(np.int16).max
|
|
104
|
+
if abs(audio_signal).max() <= 1:
|
|
105
|
+
# convert to 16bit integers (save as PCM 16 bit)
|
|
106
|
+
amplitude = np.iinfo(np.int16).max
|
|
107
|
+
audio_signal *= amplitude
|
|
108
|
+
wavfile.write(out, samplerate, audio_signal.astype(np.int16))
|
|
109
|
+
|
|
110
|
+
else:
|
|
111
|
+
return audio_signal
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def save_wav_fluidsynth(
|
|
115
|
+
input_data: Union[ScoreLike, PerformanceLike, np.ndarray],
|
|
116
|
+
out: Optional[PathLike] = None,
|
|
117
|
+
samplerate: int = SAMPLE_RATE,
|
|
118
|
+
soundfont: PathLike = DEFAULT_SOUNDFONT,
|
|
119
|
+
bpm: Union[float, np.ndarray, Callable] = 60,
|
|
120
|
+
) -> Optional[np.ndarray]:
|
|
121
|
+
"""
|
|
122
|
+
Export a score (a `Score`, `Part`, `PartGroup` or list of `Part` instances),
|
|
123
|
+
a performance (`Performance`, `PerformedPart` or list of `PerformedPart` instances)
|
|
124
|
+
as a WAV file using fluidsynth
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
input_data : ScoreLike, PerformanceLike or np.ndarray
|
|
129
|
+
A partitura object with note information.
|
|
130
|
+
out : PathLike or None
|
|
131
|
+
Path of the output Wave file. If None, the method outputs
|
|
132
|
+
the audio signal as an array (see `audio_signal` below).
|
|
133
|
+
samplerate: int
|
|
134
|
+
The sample rate of the audio file in Hz. The default is 44100Hz.
|
|
135
|
+
soundfont : PathLike
|
|
136
|
+
Path to the soundfont in SF2/SF3 format for fluidsynth.
|
|
137
|
+
bpm : float, np.ndarray, callable
|
|
138
|
+
The bpm to render the output (if the input is a score-like object).
|
|
139
|
+
See `partitura.utils.music.performance_notearray_from_score_notearray`
|
|
140
|
+
for more information on this parameter.
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
audio_signal : np.ndarray
|
|
145
|
+
Audio signal as a 1D array. Only returned if `out` is None.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
if not HAS_FLUIDSYNTH:
|
|
149
|
+
raise ImportError("Fluidsynth is not installed!")
|
|
150
|
+
|
|
151
|
+
audio_signal = synthesize_fluidsynth(
|
|
152
|
+
note_info=input_data,
|
|
153
|
+
samplerate=samplerate,
|
|
154
|
+
soundfont=soundfont,
|
|
155
|
+
bpm=bpm,
|
|
156
|
+
)
|
|
157
|
+
|
|
92
158
|
if out is not None:
|
|
93
159
|
# Write audio signal
|
|
94
|
-
|
|
160
|
+
|
|
161
|
+
# convert to 16bit integers (save as PCM 16 bit)
|
|
162
|
+
# (some DAWs cannot load audio files that are float64,
|
|
163
|
+
# e.g., Logic)
|
|
164
|
+
amplitude = np.iinfo(np.int16).max
|
|
165
|
+
if abs(audio_signal).max() <= 1:
|
|
166
|
+
# convert to 16bit integers (save as PCM 16 bit)
|
|
167
|
+
amplitude = np.iinfo(np.int16).max
|
|
168
|
+
audio_signal *= amplitude
|
|
169
|
+
wavfile.write(out, samplerate, audio_signal.astype(np.int16))
|
|
95
170
|
else:
|
|
96
171
|
return audio_signal
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
This module contains methods for exporting Kern files.
|
|
5
|
+
"""
|
|
6
|
+
import math
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
|
|
9
|
+
import numpy
|
|
10
|
+
|
|
11
|
+
import partitura.score as spt
|
|
12
|
+
from operator import itemgetter
|
|
13
|
+
from typing import Optional
|
|
14
|
+
import numpy as np
|
|
15
|
+
import warnings
|
|
16
|
+
from partitura.utils import partition, iter_current_next, to_quarter_tempo
|
|
17
|
+
from partitura.utils.misc import deprecated_alias, PathLike
|
|
18
|
+
|
|
19
|
+
__all__ = ["save_kern"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
ACC_TO_SIGN = {
|
|
23
|
+
0: "n",
|
|
24
|
+
-1: "-",
|
|
25
|
+
1: "#",
|
|
26
|
+
-2: "--",
|
|
27
|
+
2: "##",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Kern notes encoding has a dedicated octave for each note.
|
|
31
|
+
KERN_NOTES = {
|
|
32
|
+
("C", 3): "C",
|
|
33
|
+
("D", 3): "D",
|
|
34
|
+
("E", 3): "E",
|
|
35
|
+
("F", 3): "F",
|
|
36
|
+
("G", 3): "G",
|
|
37
|
+
("A", 3): "A",
|
|
38
|
+
("B", 3): "B",
|
|
39
|
+
("C", 4): "c",
|
|
40
|
+
("D", 4): "d",
|
|
41
|
+
("E", 4): "e",
|
|
42
|
+
("F", 4): "f",
|
|
43
|
+
("G", 4): "g",
|
|
44
|
+
("A", 4): "a",
|
|
45
|
+
("B", 4): "b",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
KERN_DURS = {
|
|
49
|
+
"maxima": "000",
|
|
50
|
+
"long": "00",
|
|
51
|
+
"breve": "0",
|
|
52
|
+
"whole": "1",
|
|
53
|
+
"half": "2",
|
|
54
|
+
"quarter": "4",
|
|
55
|
+
"eighth": "8",
|
|
56
|
+
"16th": "16",
|
|
57
|
+
"32nd": "32",
|
|
58
|
+
"64th": "64",
|
|
59
|
+
"128th": "128",
|
|
60
|
+
"256th": "256",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
KEYS = ["f", "c", "g", "d", "a", "e", "b"]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class KernExporter(object):
|
|
67
|
+
"""
|
|
68
|
+
Class for exporting a partitura score to Kern format.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
part: spt.Part
|
|
73
|
+
Part to export to Kern format.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, part):
|
|
77
|
+
self.part = part
|
|
78
|
+
note_array = part.note_array(include_staff=True)
|
|
79
|
+
num_measures = len(part.measures)
|
|
80
|
+
num_notes = len(part.notes)
|
|
81
|
+
num_rests = len(part.rests)
|
|
82
|
+
self.unique_voc_staff = np.unique(note_array[["voice", "staff"]], axis=0)
|
|
83
|
+
self.vocstaff_map_dict = {
|
|
84
|
+
f"{self.unique_voc_staff[i][0]}-{self.unique_voc_staff[i][1]}": i
|
|
85
|
+
for i in range(self.unique_voc_staff.shape[0])
|
|
86
|
+
}
|
|
87
|
+
# Part elements is really the maximum number of lines we could have in the kern file
|
|
88
|
+
# we add some to account for the **kern and the *- encoding at beginning and end of file and also tandem elements
|
|
89
|
+
# that might be added. We also add the number of measures to account for the measure encoding
|
|
90
|
+
total_elements_ish = num_measures + num_notes + num_rests + 2 + 10
|
|
91
|
+
self.out_data = np.empty(
|
|
92
|
+
(total_elements_ish, len(self.unique_voc_staff)), dtype=object
|
|
93
|
+
)
|
|
94
|
+
self.unique_times = np.array([p.t for p in part._points])
|
|
95
|
+
# Fill all values with the "." character to filter afterwards
|
|
96
|
+
self.out_data.fill(".")
|
|
97
|
+
self.out_data[0] = "**kern"
|
|
98
|
+
self.out_data[-1] = "*-"
|
|
99
|
+
# Add the staff element to the second line
|
|
100
|
+
for i in range(self.unique_voc_staff.shape[0]):
|
|
101
|
+
self.out_data[1, i] = f"*staff{self.unique_voc_staff[i][1]}"
|
|
102
|
+
self.prev_note_time = None
|
|
103
|
+
self.prev_note_col_idx = None
|
|
104
|
+
self.prev_note_row_idx = None
|
|
105
|
+
|
|
106
|
+
def parse(self):
|
|
107
|
+
"""
|
|
108
|
+
Parse the partitura score to Kern format.
|
|
109
|
+
|
|
110
|
+
This method iterates over all elements in the partitura score and converts them to Kern format.
|
|
111
|
+
To better process the elements, the method first groups them by start time and then processes them in order.
|
|
112
|
+
It first finds notes and then processes structural elements (clefs, time signatures, etc.) and finally measures.
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
self.out_data: np.ndarray
|
|
117
|
+
Kern file as a numpy array of strings.
|
|
118
|
+
"""
|
|
119
|
+
row_idx = 2
|
|
120
|
+
for start_time in self.unique_times:
|
|
121
|
+
end_time = start_time + 1
|
|
122
|
+
# Get all elements starting at this time
|
|
123
|
+
elements_starting = np.array(
|
|
124
|
+
list(self.part.iter_all(start=start_time, end=end_time)), dtype=object
|
|
125
|
+
)
|
|
126
|
+
# Find notes
|
|
127
|
+
note_mask = np.array(
|
|
128
|
+
[isinstance(el, spt.GenericNote) for el in elements_starting]
|
|
129
|
+
)
|
|
130
|
+
if np.any(~note_mask):
|
|
131
|
+
bar_mask = np.array(
|
|
132
|
+
[
|
|
133
|
+
isinstance(el, spt.Measure)
|
|
134
|
+
for el in elements_starting[~note_mask]
|
|
135
|
+
]
|
|
136
|
+
)
|
|
137
|
+
tandem_mask = ~bar_mask
|
|
138
|
+
structural_elements = elements_starting[~note_mask]
|
|
139
|
+
structural_elements = np.hstack(
|
|
140
|
+
(structural_elements[tandem_mask], structural_elements[bar_mask])
|
|
141
|
+
)
|
|
142
|
+
else:
|
|
143
|
+
structural_elements = elements_starting[~note_mask]
|
|
144
|
+
# Put structural elements first (start with tandem elements, then measure elements, then notes and rests)
|
|
145
|
+
elements_starting = np.hstack(
|
|
146
|
+
(structural_elements, elements_starting[note_mask])
|
|
147
|
+
)
|
|
148
|
+
for el in elements_starting:
|
|
149
|
+
add_row = True
|
|
150
|
+
if isinstance(el, spt.GenericNote):
|
|
151
|
+
self._handle_note(el, row_idx)
|
|
152
|
+
elif isinstance(el, spt.Clef):
|
|
153
|
+
# Apply clef to all voices of the same staff
|
|
154
|
+
currect_staff = el.staff
|
|
155
|
+
for staff_idx in range(self.unique_voc_staff.shape[0]):
|
|
156
|
+
if self.unique_voc_staff[staff_idx][1] == currect_staff:
|
|
157
|
+
kern_el = f"*clef{el.sign.upper()}{el.line}"
|
|
158
|
+
self.out_data[row_idx, staff_idx] = kern_el
|
|
159
|
+
elif isinstance(el, spt.Tempo):
|
|
160
|
+
# Apply tempo to all splines
|
|
161
|
+
kern_el = f"*MM{to_quarter_tempo(el.qpm)}"
|
|
162
|
+
self.out_data[row_idx] = kern_el
|
|
163
|
+
elif isinstance(el, spt.Measure):
|
|
164
|
+
# Apply measure to all splines
|
|
165
|
+
kern_el = f"={el.number}"
|
|
166
|
+
self.out_data[row_idx] = kern_el
|
|
167
|
+
elif isinstance(el, spt.TimeSignature):
|
|
168
|
+
# Apply element to all splines
|
|
169
|
+
kern_el = f"*M{el.beats}/{el.beat_type}"
|
|
170
|
+
self.out_data[row_idx] = kern_el
|
|
171
|
+
elif isinstance(el, spt.KeySignature):
|
|
172
|
+
# Apply element to all splines
|
|
173
|
+
if el.fifths < 0:
|
|
174
|
+
alters = "-".join(KEYS[: el.fifths])
|
|
175
|
+
elif el.fifths > 0:
|
|
176
|
+
alters = "#".join(KEYS[: el.fifths])
|
|
177
|
+
else:
|
|
178
|
+
alters = ""
|
|
179
|
+
kern_el = f"*k[{alters}]"
|
|
180
|
+
self.out_data[row_idx] = kern_el
|
|
181
|
+
else:
|
|
182
|
+
add_row = False
|
|
183
|
+
warnings.warn(f"Element {el} is not supported for kern export yet.")
|
|
184
|
+
if add_row:
|
|
185
|
+
row_idx += 1
|
|
186
|
+
return self.out_data
|
|
187
|
+
|
|
188
|
+
def trim(self, data):
|
|
189
|
+
# if an entire row is filled with "." elements remove it.
|
|
190
|
+
out_data = data[~np.all(data == ".", axis=1)]
|
|
191
|
+
return out_data
|
|
192
|
+
|
|
193
|
+
def sym_dur_to_kern(self, symbolic_duration: dict) -> str:
|
|
194
|
+
kern_base = KERN_DURS[symbolic_duration["type"]]
|
|
195
|
+
dots = (
|
|
196
|
+
"." * symbolic_duration["dots"]
|
|
197
|
+
if "dots" in symbolic_duration.keys()
|
|
198
|
+
else ""
|
|
199
|
+
)
|
|
200
|
+
if "actual_notes" in symbolic_duration.keys() and "normal_notes":
|
|
201
|
+
kern_base = (
|
|
202
|
+
int(kern_base)
|
|
203
|
+
* symbolic_duration["actual_notes"]
|
|
204
|
+
/ symbolic_duration["normal_notes"]
|
|
205
|
+
)
|
|
206
|
+
kern_base = str(kern_base)
|
|
207
|
+
return kern_base + dots
|
|
208
|
+
|
|
209
|
+
def duration_to_kern(self, element: spt.GenericNote) -> str:
|
|
210
|
+
if isinstance(element, spt.GraceNote):
|
|
211
|
+
if element.grace_type == "acciaccatura":
|
|
212
|
+
return "p"
|
|
213
|
+
else:
|
|
214
|
+
return "q"
|
|
215
|
+
else:
|
|
216
|
+
if "type" not in element.symbolic_duration.keys():
|
|
217
|
+
warnings.warn(f"Element {element} has no symbolic duration type")
|
|
218
|
+
return "4"
|
|
219
|
+
return self.sym_dur_to_kern(element.symbolic_duration)
|
|
220
|
+
|
|
221
|
+
def pitch_to_kern(self, element: spt.GenericNote) -> str:
|
|
222
|
+
"""
|
|
223
|
+
Transform a Partitura Note object to a kern note string (only pitch).
|
|
224
|
+
|
|
225
|
+
To encode pitch correctly in kern we need to take into account that the octave
|
|
226
|
+
duplication of the step in kern can either move the note up or down an octave
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
if isinstance(element, spt.Rest):
|
|
230
|
+
return "r"
|
|
231
|
+
step, alter, octave = element.step, element.alter, element.octave
|
|
232
|
+
# Check if we need to have duplication of the step character
|
|
233
|
+
if octave > 4:
|
|
234
|
+
multiply_character = octave - 3
|
|
235
|
+
octave = 4
|
|
236
|
+
elif octave < 3:
|
|
237
|
+
multiply_character = 4 - octave
|
|
238
|
+
octave = 3
|
|
239
|
+
else:
|
|
240
|
+
multiply_character = 1
|
|
241
|
+
# Fetch the correct string for the step and multiply it if needed
|
|
242
|
+
kern_step = KERN_NOTES[(step, octave)] * multiply_character
|
|
243
|
+
kern_alter = ACC_TO_SIGN[alter] if alter is not None else ""
|
|
244
|
+
return kern_step + kern_alter
|
|
245
|
+
|
|
246
|
+
def markings_to_kern(self, element: spt.GenericNote) -> str:
|
|
247
|
+
symbols = ""
|
|
248
|
+
if not isinstance(element, spt.Rest):
|
|
249
|
+
if element.tie_next and element.tie_prev:
|
|
250
|
+
symbols += "-"
|
|
251
|
+
elif element.tie_next:
|
|
252
|
+
symbols += "["
|
|
253
|
+
elif element.tie_prev:
|
|
254
|
+
symbols += "]"
|
|
255
|
+
if element.slur_starts:
|
|
256
|
+
symbols += "("
|
|
257
|
+
if element.slur_stops:
|
|
258
|
+
symbols += ")"
|
|
259
|
+
if isinstance(element, spt.Note):
|
|
260
|
+
if element.beam is not None:
|
|
261
|
+
symbols += (
|
|
262
|
+
"L"
|
|
263
|
+
if element.beam == "begin"
|
|
264
|
+
else "J" if element.beam == "end" else "K"
|
|
265
|
+
)
|
|
266
|
+
return symbols
|
|
267
|
+
|
|
268
|
+
def _handle_note(self, el: spt.GenericNote, row_idx) -> str:
|
|
269
|
+
voice = el.voice
|
|
270
|
+
staff = el.staff
|
|
271
|
+
duration = self.duration_to_kern(el)
|
|
272
|
+
pitch = self.pitch_to_kern(el)
|
|
273
|
+
col_idx = self.vocstaff_map_dict[f"{voice}-{staff}"]
|
|
274
|
+
markings = self.markings_to_kern(el)
|
|
275
|
+
kern_el = duration + pitch + markings
|
|
276
|
+
if self.prev_note_time == el.start.t:
|
|
277
|
+
if self.prev_note_col_idx == col_idx:
|
|
278
|
+
# Chords in Kern
|
|
279
|
+
self.out_data[self.prev_note_row_idx, self.prev_note_col_idx] = (
|
|
280
|
+
self.out_data[self.prev_note_row_idx, self.prev_note_col_idx]
|
|
281
|
+
+ " "
|
|
282
|
+
+ kern_el
|
|
283
|
+
)
|
|
284
|
+
else:
|
|
285
|
+
# Same row (start.t) other spline
|
|
286
|
+
self.out_data[self.prev_note_row_idx, col_idx] = kern_el
|
|
287
|
+
else:
|
|
288
|
+
# New line
|
|
289
|
+
self.out_data[row_idx, col_idx] = kern_el
|
|
290
|
+
self.prev_note_row_idx = row_idx
|
|
291
|
+
self.prev_note_col_idx = col_idx
|
|
292
|
+
self.prev_note_time = el.start.t
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def save_kern(
|
|
296
|
+
score_data: spt.ScoreLike,
|
|
297
|
+
out: Optional[PathLike] = None,
|
|
298
|
+
) -> Optional[np.ndarray]:
|
|
299
|
+
"""
|
|
300
|
+
Save a score in Kern format.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
score_data: spt.ScoreLike
|
|
305
|
+
Score to save in Kern format
|
|
306
|
+
|
|
307
|
+
out: Optional[PathLike]
|
|
308
|
+
Path to save the Kern file. If None, the function returns the Kern file as a numpy array.
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
Optional[np.ndarray]
|
|
313
|
+
If out is None, the Kern file is returned as a numpy array.
|
|
314
|
+
"""
|
|
315
|
+
# Header extracts meta information about the score
|
|
316
|
+
header = "Here is some random piece"
|
|
317
|
+
# Kern can output only from part so first let's merge parts (we need a timewise representation)
|
|
318
|
+
if isinstance(score_data, spt.Score):
|
|
319
|
+
# TODO check that divisions are the same
|
|
320
|
+
part = spt.merge_parts(score_data.parts)
|
|
321
|
+
else:
|
|
322
|
+
part = score_data
|
|
323
|
+
if not part.measures:
|
|
324
|
+
spt.add_measures(part)
|
|
325
|
+
spt.fill_rests(part, measurewise=False)
|
|
326
|
+
exporter = KernExporter(part)
|
|
327
|
+
out_data = exporter.parse()
|
|
328
|
+
out_data = exporter.trim(out_data)
|
|
329
|
+
# Use numpy savetxt to save the file
|
|
330
|
+
footer = "Encoded using the Partitura Python package, version 1.5.0"
|
|
331
|
+
if out is not None:
|
|
332
|
+
np.savetxt(
|
|
333
|
+
fname=out,
|
|
334
|
+
X=out_data,
|
|
335
|
+
fmt="%1.26s",
|
|
336
|
+
delimiter="\t",
|
|
337
|
+
newline="\n",
|
|
338
|
+
header=header,
|
|
339
|
+
footer=footer,
|
|
340
|
+
comments="!!!",
|
|
341
|
+
encoding="utf-8",
|
|
342
|
+
)
|
|
343
|
+
else:
|
|
344
|
+
return out_data
|