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

Fix Issue #414 #416

Merged
merged 1 commit into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 24 additions & 3 deletions partitura/io/importmusicxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import partitura.score as score
from partitura.score import assign_note_ids
from partitura.utils import ensure_notearray
from partitura.utils.misc import deprecated_alias, deprecated_parameter, PathLike
from partitura.utils.misc import deprecated_alias, deprecated_parameter, PathLike, parse_ints

__all__ = ["load_musicxml", "musicxml_to_notearray"]

Expand Down Expand Up @@ -1677,8 +1677,29 @@ def get_technical_notations(e: etree._Element) -> List[score.NoteTechnicalNotati

def parse_fingering(e: etree._Element) -> score.Fingering:

fingering = score.Fingering(fingering=int(e.text))

try:
# There seems to be a few cases with fingerings encoded like 4_1.
# This is not standard in MusicXML according to the documentation,
# but since it appears in files from the web, and can be displayed
# with MuseScore, the solution for now is just to take the fist value.
finger_info = parse_ints(e.text)
except Exception as e:
# Do not raise an error if fingering info cannot be parsed, insted
# just set it as None.
warnings.warn(f"Cannot parse fingering info for {e.text}!")
finger_info = [None]

is_alternate = e.attrib.get("alternate", False)
is_substitution = e.attrib.get("substitution", False)
placement = e.attrib.get("placement", None)

# If there is more than one finger, only take the first one
fingering = score.Fingering(
fingering=finger_info[0],
is_substitution=is_alternate,
is_alternate=is_alternate,
placement=placement,
)
return fingering


Expand Down
33 changes: 32 additions & 1 deletion partitura/score.py
Original file line number Diff line number Diff line change
Expand Up @@ -3396,12 +3396,43 @@ def __init__(self, type: str, info: Optional[Any] = None) -> None:


class Fingering(NoteTechnicalNotation):
def __init__(self, fingering: int) -> None:
"""
This object represents fingering. For now, it supports attributes
present in MusicXML:

https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/fingering/

Parameters
----------
fingering : Optional[int]
Fingering information. Can be None (usually the result of incorrect parsing of fingering).

is_substitution: bool
Whether this fingering is a substitution in the middle of a note. Default is False

is_alternate: bool
Whether this fingering is an alternative fingering. Default is False

placement: str
Placement of the fingering (above or below a note)
"""

def __init__(
self,
fingering: Optional[int],
is_substitution: bool = False,
placement: Optional[str] = None,
is_alternate: bool = False,
) -> None:
super().__init__(
type="fingering",
info=fingering,
)
self.fingering = fingering
self.is_alternate = is_alternate
self.alternative_fingering = []
self.is_substitution = is_substitution
self.placement = placement


class PartGroup(object):
Expand Down
23 changes: 22 additions & 1 deletion partitura/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import warnings
from urllib.request import urlopen
from shutil import copyfileobj
import re

from typing import Union, Callable, Dict, Any, Iterable, Optional
from typing import Union, Callable, Dict, Any, Iterable, Optional, List

import numpy as np

Expand Down Expand Up @@ -280,3 +281,23 @@ def download_file(
"""
with urlopen(url) as in_stream, open(out, "wb") as out_file:
copyfileobj(in_stream, out_file)


def parse_ints(input_string: str) -> List[int]:
"""
Parse all numbers from a given string where numbers are separated by spaces or tabs.

Parameters
----------
input_string : str
The input string containing numbers separated by spaces or tabs.

Returns
-------
List[int]
A list of integers extracted from the input string.
"""
# Regular expression to match numbers
pattern = r'\d+'
# Find all matches and convert them to integers
return list(map(int, re.findall(pattern, input_string)))
Loading