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

Excerpts allowing for mc_onset positions [FEATURE] #94

Open
johentsch opened this issue Aug 31, 2023 · 0 comments
Open

Excerpts allowing for mc_onset positions [FEATURE] #94

johentsch opened this issue Aug 31, 2023 · 0 comments
Assignees
Labels
enhancement New feature or request

Comments

@johentsch
Copy link
Owner

Is your feature request related to a problem? Please describe.
Currently (v2.1.1) excerpts can only be created to include entire MC units. It would be great to be able to

  • cut off the beginning of the first MC before a given mc_onset and/or
  • cut off the ending of the last MC starting from a given mc_onset.

Describe the solution you'd like

Additional optional arguments start_mc_onset and end_mc_onset for the bs4_parser.Excerpt class which can be any number, most typically a Fraction.

For the start_mc_onset, it is not trivial to remove XML elements while maintaining correct positions of the remaining ones (can be done but much more convoluted because positions are relative and implicit). So the easiest solution might be to replace all note events before the given onset with rests. It remains to be seen how we would deal with the remaining score elements (harmony labels, dynamic marks, staff text, etc.). One solution might lie in making them invisible.

In terms of the end_mc_onset, the ideal solution would be if we could emulate the functionality in MuseScore that lets you select a measure and set the "Actual duration" in the Measure Properties to the desired value, which correctly truncates it (although it removes existing notes overlapping the new end of the bar rather than shortening them). In principle, it should be unproblematic to remove events after a given point because it does not affect any relevant positions. But for consistency we might as well use the same mechanism as for the start_mc_onset (replace notes with rests), which also would be adventageous for the following consideration.

In both cases we should decide how to deal with overlapping note events, which may, or may not, lead to more "organic" excerpts, e.g. by

  • having an excerpt begin with the one note that starts earlier but overlaps "into it" or
  • letting notes that overlap the end_mc_onset "ring" rather than cutting them or, which would be worse, removing/replacing them.

This should probably become a boolean argument keep_overlapping to let the user decide.

Mechanics, avenues, pointers

  • The new arguments go into Excerpt and into the _MSCX_bs4.make_excerpt() method that creates and returns it.

  • The manipulation of the excerpt will be triggered, depending on the given arguments, by calling

    • Excerpt.replace_notes_before_onset()
    • Excerpt.replace_notes_after_onset()
  • both of which might be recurring to the same underlying method under the hood.

  • An example of how to access the relevant tags in the XML tree can be seen here in the method _MSCX_bs4.color_notes(), with the difference that we can address the MC in question right away instead of iterating over all MCs. self.tags is a nested dictionary/JSON thingy with the following structure:

    {MC -> 
       {staff -> 
          {voice -> 
            {mc_onset -> 
               [{"name" -> str,
                 "duration" -> Fraction,
                 "tag" -> bs4.Tag
                 },
                 ...
               ]
            } 
          } 
       } 
     }
    
  • This means we can

    • get the dict for the given start or end MC
    • iterate over all staves
    • iterate over all voice
    • iterate over all mc_onsets before or starting from the given values (with continue for the other values).
    • iterate over the 3-item info dicts for all tags at a given onset
    • act on the tags according to their names
      • bs4.Tag.decompose() deletes a tag
      • self.new_tag() (a method inherited from _MSCX_bs4) conveniently inserts a new tag by specifying a parent or sibling for reference, BUT
  • maybe the easiest approach for replacing the tag is by simply modifying the existing one (otherwise one would have to store one of its siblings first to pass it to .new_tag()). Let's consider the task: In MuseScore, every note is contained in a <Chord> and all notes in the same chord have the same onset and duration:

    <Chord>
      <durationType>value</durationType>
      <Note>...</Note>
      ...
    </Chord>
    

    The goal is to replace this whole structure with

    <Rest>
      <durationType>value</durationType>
    </Rest>
    
  • To achieve this, we can simply change transform the <Chord> tag via bs4.Tag.name = "Rest" and then either

    • iterate over all children (bs4.Tag.find_all()) removing all except the durationType, or
    • store the durationType value, remove all children (bs4.Tag.clear()) and create a new durationType tag with the stored value (using self.new_tag("durationType", value, append_within=rest_tag))

Future

Suggestion to keep the high-level interface in score.py (to be added later) simpler by restricting the mechanism to mc_onset values, too, as opposed to alternatively allowing for mn_onset.

@johentsch johentsch added the enhancement New feature or request label Aug 31, 2023
@johentsch johentsch self-assigned this Aug 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant