1+ # flake8: noqa: S101, B011
2+
13import glob
24import math
5+ import re
36from xml .dom import minidom
47
58from homr import constants
@@ -82,6 +85,9 @@ def add_bar_line(self, bar_line: BarLine, is_first: bool) -> None:
8285 if not already_present :
8386 self .bar_line_x_positions .add (bar_line .x )
8487
88+ def remove_bar_line (self , x : int ) -> None :
89+ self .bar_line_x_positions .discard (x )
90+
8591 def merge_staff (self , other : "SvgStaff" ) -> "SvgStaff" :
8692 if self .number_of_measures != other .number_of_measures :
8793 raise ValueError ("Can't merge staffs with a different number of measures" )
@@ -186,6 +192,27 @@ def get_position_from_multiple_svg_files(musicxml_file: str) -> list[SvgMusicFil
186192 return result
187193
188194
195+ def _parse_note_position (note : minidom .Element ) -> tuple [float , float ]:
196+ # collect note/rest:
197+ # Older MuseScore: <path transform="matrix(a,b,c,d,e,f)"/> where (e, f) is the position.
198+ # Newer MuseScore: <path class="Note" d="M<x>,<y> ..."/> where d is the position.
199+ transform = note .getAttribute ("transform" )
200+ if transform :
201+ match = re .search (r"matrix\(([^)]*)\)" , transform )
202+ assert match , f"Could not parse note transform: { transform } "
203+ values = [float (v ) for v in match .group (1 ).split ("," )]
204+ return values [4 ], values [5 ]
205+
206+ class_name = note .getAttribute ("class" )
207+ if class_name in ("Note" , "Rest" ):
208+ points = note .getAttribute ("d" )
209+ match = re .search (r"[Mm]\s*(-?[\d.]+)[\s,]+(-?[\d.]+)" , points )
210+ assert match , f"Could not parse note position: { points } "
211+ return float (match .group (1 )), float (match .group (2 ))
212+
213+ assert False , f"Unexpected element for note position: { class_name !r} "
214+
215+
189216def _parse_paths (points : str ) -> SvgRectangle :
190217 [start , end ] = points .split ()
191218 [x1 , y1 ] = start .split ("," )
@@ -285,6 +312,47 @@ def _extend_staffs_with_stems(staffs: list[SvgStaff], stems: list[SvgRectangle])
285312 best_staff .extend_y_range (stem .y + stem .height )
286313
287314
315+ def _staff_has_content_between (
316+ staff : SvgStaff , notes : list [tuple [float , float ]], start : float , end : float
317+ ) -> bool :
318+ # there're 2 hard-coded tolerances to fit some corner cases.
319+ x_tolerance = 5.0
320+ y_tolerance = 60.0
321+ return any (
322+ staff .x <= mx <= staff .x + staff .width
323+ and (staff .y - y_tolerance ) <= my <= (staff .y + staff .height + y_tolerance )
324+ and (start - x_tolerance ) <= mx < end
325+ for mx , my in notes
326+ )
327+
328+
329+ def _remove_empty_measures (
330+ svg_file : str , staffs : list [SvgStaff ], notes : list [tuple [float , float ]]
331+ ) -> None :
332+ # split `staffs` into several systems.
333+ # each system share the same barline x positions
334+ systems : dict [tuple [int , ...], list [SvgStaff ]] = {}
335+ for staff in staffs :
336+ key = tuple (sorted (staff .bar_line_x_positions ))
337+ systems .setdefault (key , []).append (staff )
338+
339+ for bar_line_x_positions , staffs_in_system in systems .items ():
340+ for i in range (len (bar_line_x_positions ) - 1 ):
341+ start = bar_line_x_positions [i ]
342+ end = bar_line_x_positions [i + 1 ]
343+ has_content = any (
344+ _staff_has_content_between (staff , notes , start , end ) for staff in staffs_in_system
345+ )
346+ if has_content :
347+ continue
348+ # Typically we drop the barline at the end, but if
349+ # this is the last measure, we drop barline at begin.
350+ is_trailing = i + 1 == len (bar_line_x_positions ) - 1
351+ boundary = start if is_trailing else end
352+ for staff in staffs_in_system :
353+ staff .remove_bar_line (boundary )
354+
355+
288356def get_position_information_from_svg (svg_file : str ) -> SvgMusicFile :
289357 doc = minidom .parse (svg_file ) # noqa: S318
290358 try :
@@ -310,11 +378,19 @@ def get_position_information_from_svg(svg_file: str) -> SvgMusicFile:
310378 if class_name == "Stem" :
311379 stems .append (_parse_paths (line .getAttribute ("points" )))
312380
381+ notes : list [tuple [float , float ]] = []
382+ for path in doc .getElementsByTagName ("path" ):
383+ class_name = path .getAttribute ("class" )
384+ if class_name in ("Note" , "Rest" ):
385+ notes .append (_parse_note_position (path ))
386+
313387 combined = _combine_staff_lines_and_bar_lines (staff_lines , bar_lines )
314388
315389 # Extend staffs using stem information
316390 _extend_staffs_with_stems (combined , stems )
317391
392+ _remove_empty_measures (svg_file , combined , notes )
393+
318394 return SvgMusicFile (svg_file , width , height , combined )
319395 finally :
320396 doc .unlink ()
0 commit comments