Skip to content

Commit

Permalink
fix issue parsing incorrectly formatted fingering in MusicXML
Browse files Browse the repository at this point in the history
  • Loading branch information
CarlosCancino-Chacon committed Jan 18, 2025
1 parent 356429d commit 0680726
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 5 deletions.
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)))

0 comments on commit 0680726

Please sign in to comment.