Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR for Release 1.4.0 #320

Merged
merged 96 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
a9ee5cd
new class for performedNotes.
manoskary Jul 18, 2023
966034e
add support for musescore 4
fosfrancesco Jul 27, 2023
414e54e
correct musescore import bug
fosfrancesco Jul 27, 2023
eae4d4f
added tests and corrected problem in kern import which was not throwi…
fosfrancesco Jul 28, 2023
660b748
update workflow:
manoskary Jul 28, 2023
35eba7a
added skiptest for github actions
fosfrancesco Jul 28, 2023
2671711
Merge branch 'musescore4' of https://github.com/CPJKU/partitura into …
fosfrancesco Jul 28, 2023
333c8b9
update workflow:
manoskary Jul 28, 2023
e63970a
Merge remote-tracking branch 'origin/musescore4' into musescore4
manoskary Jul 28, 2023
eff27f3
Attempt to fix syntax error on workflow file.
manoskary Jul 28, 2023
3488684
Third Attempt to fix syntax error on workflow file.
manoskary Jul 28, 2023
6b33d9a
better function naming in test_load_score test. Stop condition for ba…
fosfrancesco Jul 28, 2023
120a918
Merge branch 'musescore4' of https://github.com/CPJKU/partitura into …
fosfrancesco Jul 28, 2023
3befc24
some MEI bugfixes
fosfrancesco Aug 1, 2023
dc01764
Deleted 4 useless test files, bugfixes on MEI import with strange tup…
fosfrancesco Aug 2, 2023
0f37837
removed problematic test that was using musescore 4
fosfrancesco Aug 2, 2023
302adcf
removed file
fosfrancesco Aug 2, 2023
31aa38c
Measures have only start and no end, like in import musicxml. Correct…
fosfrancesco Aug 2, 2023
920856d
add force flags for musescore
fosfrancesco Aug 2, 2023
c25cd52
updated workflow to install musescore3
fosfrancesco Aug 2, 2023
4e883e7
Removed Musescore Tests, because they can't run on github
fosfrancesco Aug 2, 2023
1ce4a02
added unfolding part minimal:
manoskary Aug 3, 2023
4be4bbe
new score attributes and score prop line for match export, new Score …
huispaty Aug 3, 2023
0f24a16
Minor edit on line break to re-launch tests
manoskary Aug 3, 2023
5c12147
Suggested edit by review.
manoskary Aug 3, 2023
b9ff8db
updated load_score test to a better coding style
fosfrancesco Aug 4, 2023
f919945
Merge pull request #302 from CPJKU/unfolding_updates_
sildater Aug 4, 2023
a5993b0
Format code with black (bot)
sildater Aug 4, 2023
5fe996d
Merge pull request #296 from CPJKU/musescore4
manoskary Aug 4, 2023
7fdd582
Format code with black (bot)
manoskary Aug 4, 2023
4d73f13
removed pandas dependency and print statements, added score attribute…
huispaty Aug 8, 2023
30ee252
efficient voice overlap filtering, changed test file access;
huispaty Aug 8, 2023
98df8be
del print statements
huispaty Aug 8, 2023
66ca02a
more efficient voice overlap note identification
huispaty Aug 10, 2023
c115949
Fixed ppart_from_matchfile when first_note_at_zero:
manoskary Aug 10, 2023
d091563
fixes for time_slice from ppart. Changed initialization of ppart towa…
manoskary Aug 10, 2023
76381eb
Addresses #306
manoskary Aug 10, 2023
5f18fd8
add a bugfix for midi scores that only report time signature in one t…
fosfrancesco Aug 18, 2023
936e673
mei measure number is now an integer, and. Measure.name is the string…
fosfrancesco Aug 23, 2023
cc28af0
measure number starts from 1 to behave like musicxml import
fosfrancesco Aug 29, 2023
9cb7eba
Solved a bug which was resetting the measure counter in case of
fosfrancesco Aug 31, 2023
b7c1b35
Merge pull request #310 from CPJKU/mei_measure_number
fosfrancesco Aug 31, 2023
3080de3
Format code with black (bot)
fosfrancesco Aug 31, 2023
c10bfab
fixed bug in case of match files
fosfrancesco Sep 8, 2023
9ce24be
use os function to get the file extension
fosfrancesco Sep 12, 2023
d453266
Merge pull request #314 from CPJKU/load_score_from_extension
manoskary Sep 13, 2023
dd5b2ff
Format code with black (bot)
manoskary Sep 13, 2023
aaf292e
solution for empty dynamic directions.
manoskary Sep 14, 2023
19af14c
added empty dynamics direction to adress issue #315 and its solution.
manoskary Sep 15, 2023
32cf670
adapted code to proposed solution, if iterable has no next element de…
manoskary Sep 15, 2023
03a88ef
Merge pull request #316 from CPJKU/musicxml_import_fix
fosfrancesco Sep 15, 2023
baa2fee
A unit test example for cross staff beaming. The possibility of testi…
manoskary Sep 20, 2023
a2e52b8
minor typo correction.
manoskary Sep 20, 2023
b821956
Merge pull request #308 from CPJKU/score_midi_bug
CarlosCancino-Chacon Sep 20, 2023
0cea6b0
Format code with black (bot)
Sep 20, 2023
b7e2805
corrections requested by reviewers.
manoskary Sep 20, 2023
c7b4b36
Removed warnings.
manoskary Sep 20, 2023
51efb97
Merge pull request #317 from CPJKU/cross_staff_beaming
fosfrancesco Sep 20, 2023
3fa85f7
Merge pull request #307 from CPJKU/fast_matchfile_parsing
CarlosCancino-Chacon Sep 20, 2023
c7768cd
Format code with black (bot)
Sep 20, 2023
6028327
removed performednote repr method to inherit from parent class.
manoskary Sep 20, 2023
6a2ceac
Fixes #304
manoskary Sep 21, 2023
9f231e3
Merge pull request #295 from CPJKU/performance_notes
CarlosCancino-Chacon Sep 21, 2023
4027a9a
Format code with black (bot)
Sep 21, 2023
f67a331
removed special parsing for mozart scores
huispaty Sep 21, 2023
c143358
corrected mozart var testfile
huispaty Sep 21, 2023
4838419
Merge pull request #303 from CPJKU/match_export
manoskary Sep 22, 2023
1262c54
Format code with black (bot)
manoskary Sep 22, 2023
70daa16
Merge pull request #318 from CPJKU/deepcopy_score
huispaty Sep 22, 2023
e0db041
Update conf.py for release 1.3.2
huispaty Sep 22, 2023
70f558f
Update CHANGES.md
huispaty Sep 22, 2023
f0ffdd6
Update setup.py
huispaty Sep 22, 2023
63e7419
Update CHANGES.md
huispaty Sep 22, 2023
6579a0f
Update __init__.py
huispaty Sep 22, 2023
8c15d14
Format code with black (bot)
huispaty Sep 22, 2023
e383d62
Update importkern.py
huispaty Sep 22, 2023
7bdcc78
Update setup.py
huispaty Sep 22, 2023
0fce076
Update test_musescore.py
huispaty Sep 22, 2023
5a400bd
Update conf.py
huispaty Sep 22, 2023
adc00db
Update CHANGES.md
huispaty Sep 22, 2023
69bfe5a
fixed musescore testing
huispaty Sep 22, 2023
027344e
Update musescore.py
manoskary Sep 22, 2023
4e9e1d9
Format code with black (bot)
manoskary Sep 22, 2023
eb94c13
reverted performance note numbering
huispaty Sep 25, 2023
86d2420
update CHANGES.md
huispaty Sep 25, 2023
c7e24ce
Changed PerformedNote object to not directly inherit from a dict but …
manoskary Sep 25, 2023
b6bed0e
Changed PerformedNote object to not directly inherit from a dict but …
manoskary Sep 25, 2023
7b68675
Format code with black (bot)
manoskary Sep 25, 2023
b838081
Added a copy method to PerformedNote.
manoskary Sep 25, 2023
ac42599
Merge remote-tracking branch 'origin/develop' into develop
manoskary Sep 25, 2023
e279f2d
Added note_on_tick and note_off_tick to accepted keys.
manoskary Sep 25, 2023
702adf7
Corrections on validation of PerformedNotes.
manoskary Sep 25, 2023
2e58c9b
Format code with black (bot)
manoskary Sep 25, 2023
6151ee9
Added tick validation in the loop.
manoskary Sep 25, 2023
f3ce4ef
Merge remote-tracking branch 'origin/develop' into develop
manoskary Sep 25, 2023
fe4dbea
Workaround to check for screen only on linux machines to avoid missin…
manoskary Sep 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/partitura_unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
pip install -r requirements.txt
pip install .
- name: Install Optional dependencies
run: |
run: |
pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1
pip install miditok==2.0.6 tokenizers==0.13.3
- name: Run Tests
Expand Down
27 changes: 27 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
Release Notes
=============

Version 1.4.0 (Released on 2023-09-22)
--------------------------------------

New Features
------------
* new class for performed notes
* minimal unfolding for part
* updated Musescore parser for version 4
* `load_score` auto-selects parser based on file type
* new attributes for `Score` object for capturing meta information
* new score note attributes in matchfile export (`grace`, `voice_overlap`)
* new `tempo_indication` score property line in matchfile export

Bug Fixes
------------
* Fixed bug: #297
* Fixed bug: #304
* Fixed bug: #306
* Fixed bug: #308
* Fixed bug: #310
* Fixed bug: #315

Other Changes
------------
* new unit test for cross-staff beaming for musicxml


Version 1.3.1 (Released on 2023-07-06)
--------------------------------------

Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
# built documents.
#
# The short X.Y version.
version = "1.3.1" # pkg_resources.get_distribution("partitura").version
version = "1.4.0" # pkg_resources.get_distribution("partitura").version
# The full version, including alpha/beta/rc tags.
release = "1.3.1"
release = "1.4.0"

# # The full version, including alpha/beta/rc tags
# release = pkg_resources.get_distribution("partitura").version
Expand Down
3 changes: 3 additions & 0 deletions partitura/directions.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ def unabbreviate(s):
"adagio",
"agitato",
"andante",
"andante cantabile",
"andante amoroso",
"andantino",
"animato",
"appassionato",
Expand Down Expand Up @@ -193,6 +195,7 @@ def unabbreviate(s):
"tranquilamente",
"tranquilo",
"recitativo",
"allegro moderato",
r"/(vivo|vivacissimamente|vivace)/",
r"/(allegro|allegretto)/",
r"/(espressivo|espress\.?)/",
Expand Down
76 changes: 41 additions & 35 deletions partitura/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
This module contains methods for importing and exporting symbolic music formats.
"""
from typing import Union
import os

from .importmusicxml import load_musicxml
from .importmidi import load_score_midi, load_performance_midi
Expand Down Expand Up @@ -35,7 +36,7 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score:
"""
Load a score format supported by partitura. Currently the accepted formats
are MusicXML, MIDI, Kern and MEI, plus all formats for which
MuseScore has support import-support (requires MuseScore 3).
MuseScore has support import-support (requires MuseScore 4 or 3).

Parameters
----------
Expand All @@ -54,20 +55,16 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score:
scr: :class:`partitura.score.Score`
A score instance.
"""
part = None

# Catch exceptions
exception_dictionary = dict()
# Load MusicXML
try:
extension = os.path.splitext(filename)[-1].lower()
if extension in (".mxl", ".xml", ".musicxml"):
# Load MusicXML
return load_musicxml(
filename=filename,
force_note_ids=force_note_ids,
)
except Exception as e:
exception_dictionary["MusicXML"] = e
# Load MIDI
try:
elif extension in [".midi", ".mid"]:
# Load MIDI
if (force_note_ids is None) or (not force_note_ids):
assign_note_ids = False
else:
Expand All @@ -76,44 +73,53 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score:
filename=filename,
assign_note_ids=assign_note_ids,
)
except Exception as e:
exception_dictionary["MIDI"] = e
# Load MEI
try:
elif extension in [".mei"]:
# Load MEI
return load_mei(filename=filename)
except Exception as e:
exception_dictionary["MEI"] = e
# Load Kern
try:
elif extension in [".kern", ".krn"]:
return load_kern(
filename=filename,
force_note_ids=force_note_ids,
)
except Exception as e:
exception_dictionary["Kern"] = e
# Load MuseScore
try:
elif extension in [
".mscz",
".mscx",
".musescore",
".mscore",
".ms",
".kar",
".md",
".cap",
".capx",
".bww",
".mgu",
".sgu",
".ove",
".scw",
".ptb",
".gtp",
".gp3",
".gp4",
".gp5",
".gpx",
".gp",
]:
# Load MuseScore
return load_via_musescore(
filename=filename,
force_note_ids=force_note_ids,
)
except Exception as e:
exception_dictionary["MuseScore"] = e
try:
elif extension in [".match"]:
# Load the score information from a Matchfile
_, _, part = load_match(
_, _, score = load_match(
filename=filename,
create_score=True,
)

except Exception as e:
exception_dictionary["matchfile"] = e
if part is None:
for score_format, exception in exception_dictionary.items():
print(f"Error loading score as {score_format}:")
print(exception)

raise NotSupportedFormatError
return score
else:
raise NotSupportedFormatError(
f"{extension} file extension is not supported. If this should be supported, consider editing partitura/io/__init__.py file"
)


def load_score_as_part(filename: PathLike) -> Part:
Expand Down
54 changes: 51 additions & 3 deletions partitura/io/exportmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
FractionalSymbolicDuration,
MatchKeySignature,
MatchTimeSignature,
MatchTempoIndication,
Version,
)

Expand Down Expand Up @@ -71,6 +72,8 @@ def matchfile_from_alignment(
score_filename: Optional[PathLike] = None,
performance_filename: Optional[PathLike] = None,
assume_part_unfolded: bool = False,
tempo_indication: Optional[str] = None,
diff_score_version_notes: Optional[list] = None,
version: Version = LATEST_VERSION,
debug: bool = False,
) -> MatchFile:
Expand Down Expand Up @@ -106,6 +109,10 @@ def matchfile_from_alignment(
repetitions in the alignment. If False, the part will be automatically
unfolded to have maximal coverage of the notes in the alignment.
See `partitura.score.unfold_part_alignment`.
tempo_indication : str or None
The tempo direction indicated in the beginning of the score
diff_score_version_notes : list or None
A list of score notes that reflect a special score version (e.g., original edition/Erstdruck, Editors note etc.)
version: Version
Version of the match file. For now only 1.0.0 is supported.
Returns
Expand Down Expand Up @@ -199,7 +206,6 @@ def matchfile_from_alignment(

# Score prop header lines
scoreprop_lines = defaultdict(list)

# For score notes
score_info = dict()
# Info for sorting lines
Expand Down Expand Up @@ -276,7 +282,6 @@ def matchfile_from_alignment(
# Get all notes in the measure
snotes = spart.iter_all(score.Note, m.start, m.end, include_subclasses=True)
# Beginning of each measure

for snote in snotes:
onset_divs, offset_divs = snote.start.t, snote.start.t + snote.duration_tied
duration_divs = offset_divs - onset_divs
Expand Down Expand Up @@ -324,6 +329,15 @@ def matchfile_from_alignment(
if fermata is not None:
score_attributes_list.append("fermata")

if isinstance(snote, score.GraceNote):
score_attributes_list.append("grace")

if (
diff_score_version_notes is not None
and snote.id in diff_score_version_notes
):
score_attributes_list.append("diff_score_version")

score_info[snote.id] = MatchSnote(
version=version,
anchor=str(snote.id),
Expand All @@ -346,6 +360,22 @@ def matchfile_from_alignment(
)
snote_sort_info[snote.id] = (onset_beats, snote.doc_order)

# # NOTE time position is hardcoded, not pretty... Assumes there is only one tempo indication at the beginning of the score
if tempo_indication is not None:
score_tempo_direction_header = make_scoreprop(
version=version,
attribute="tempoIndication",
value=MatchTempoIndication(
tempo_indication,
is_list=False,
),
measure=measure_starts[0][0],
beat=1,
offset=0,
time_in_beats=measure_starts[0][2],
)
scoreprop_lines["tempo_indication"].append(score_tempo_direction_header)

perf_info = dict()
pnote_sort_info = dict()
for pnote in ppart.notes:
Expand All @@ -372,6 +402,21 @@ def matchfile_from_alignment(

sort_stime = []
note_lines = []

# Get ids of notes which voice overlap
sna = spart.note_array()
onset_pitch_slice = sna[["onset_div", "pitch"]]
uniques, counts = np.unique(onset_pitch_slice, return_counts=True)
duplicate_values = uniques[counts > 1]
duplicates = dict()
for v in duplicate_values:
idx = np.where(onset_pitch_slice == v)[0]
duplicates[tuple(v)] = idx
voice_overlap_note_ids = []
if len(duplicates) > 0:
duplicate_idx = np.concatenate(np.array(list(duplicates.values()))).flatten()
voice_overlap_note_ids = list(sna[duplicate_idx]["id"])

for al_note in alignment:
label = al_note["label"]

Expand All @@ -384,6 +429,8 @@ def matchfile_from_alignment(

elif label == "deletion":
snote = score_info[al_note["score_id"]]
if al_note["score_id"] in voice_overlap_note_ids:
snote.ScoreAttributesList.append("voice_overlap")
deletion_line = MatchSnoteDeletion(version=version, snote=snote)
note_lines.append(deletion_line)
sort_stime.append(snote_sort_info[al_note["score_id"]])
Expand Down Expand Up @@ -441,6 +488,7 @@ def matchfile_from_alignment(
"clock_rate",
"key_signatures",
"time_signatures",
"tempo_indication",
]
all_match_lines = []
for h in header_order:
Expand Down Expand Up @@ -537,7 +585,7 @@ def save_match(
else:
raise ValueError(
"`performance_data` should be a `Performance`, a `PerformedPart`, or a "
f"list of `PerformedPart` objects, but is {type(score_data)}"
f"list of `PerformedPart` objects, but is {type(performance_data)}"
)

# Get matchfile
Expand Down
10 changes: 8 additions & 2 deletions partitura/io/exportmidi.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from collections import defaultdict, OrderedDict
from typing import Optional, Iterable

from mido import MidiFile, MidiTrack, Message, MetaMessage
from mido import MidiFile, MidiTrack, Message, MetaMessage, merge_tracks

import partitura.score as score
from partitura.score import Score, Part, PartGroup, ScoreLike
Expand Down Expand Up @@ -87,6 +87,7 @@ def save_performance_midi(
mpq: int = 500000,
ppq: int = 480,
default_velocity: int = 64,
merge_tracks_save: Optional[bool] = False,
) -> Optional[MidiFile]:
"""Save a :class:`~partitura.performance.PerformedPart` or
a :class:`~partitura.performance.Performance` as a MIDI file
Expand All @@ -107,6 +108,8 @@ def save_performance_midi(
default_velocity : int, optional
A default velocity value (between 0 and 127) to be used for
notes without a specified velocity. Defaults to 64.
merge_tracks_save : bool, optional
Determines whether midi tracks are merged when exporting to a midi file. Defaults to False.

Returns
-------
Expand Down Expand Up @@ -134,7 +137,6 @@ def save_performance_midi(
)

track_events = defaultdict(lambda: defaultdict(list))

for performed_part in performed_parts:
for c in performed_part.controls:
track = c.get("track", 0)
Expand Down Expand Up @@ -217,6 +219,10 @@ def save_performance_midi(
track.append(msg.copy(time=t_delta))
t_delta = 0
t = t_msg

if merge_tracks_save and len(mf.tracks) > 1:
mf.tracks = [merge_tracks(mf.tracks)]

if out is not None:
if hasattr(out, "write"):
mf.save(file=out)
Expand Down
Loading
Loading