GSoC 2020 (Project Introduction): Tree Model Refactoring for libmscore

Posted 6 months ago

Hello! I’m Kumar Kartikay, an undergrad computer science student from NIT Hamirpur, India. I have been selected this year in Google Summer of Code to work with MuseScore. I’m going to be working on a project involving refactoring of code in libmscore, which is the part of the code that handles most of the MuseScore internals, like the Score and the notes in it.

Here is a diagram of the current code structure in libmscore (click to open full sized image):
Fig2_2.jpg

The central line of classes represents the backbone of MuseScore’s classes, which are shown in the red box. You read it as follows: Score has a MasterScore (which is a single movement), MasterScore has a Page, Page has a System... etc, ultimately reaching Notes, Accidentals etc. While these classes are shown in a line, they actually represent a tree, because the MasterScore can have more than one Page, and each Page can have more than one System, etc. There can only be one Score so we can consider it to be the root of the tree

We say that Score is the parent of Page, and there is a function Page->parent() that returns the score. However, there is no function Score->child(n) that returns the n-th page. Instead, you have to already know that Page is the child of Score and call Score->page(n). The main aim is to add a generic element->getchild(int n) function to either the Element or ScoreElement class so that there is always a way to access children. A secondary aim is to rearrange some of the classes in the tree to create a structure that is more logical.

Project Details

Basically the current situation is that in libmscore, the classes already follow a bit of a hierarchical structure. However, we cannot do tree traversals in the code because there is no way to generically access children of any element, even though we have a parent element for each element.
There are two classes, Element and ScoreElement, from which all the objects in the score are derived. We can select the children elements like note->accidental() or note->dot(), but there is no way of doing something like element->children() or element->getChild(n). If such a way is added, we’d be able to do tree traversals and simplify some code like writing and reading files and drawing etc.

For example, currently the code to read and write files goes through each and every class and writes its properties and then calls it’s children’s write functions. Here is the Note::write() function for instance, which goes through accidentals, dots, ties, etc. to write the note. The whole sequence to read a note is: MasterScore::read() -> Score::read() -> Score::readStaff() -> Measure::read() -> Measure::readVoice() -> Chord::read() -> Chord::readProperties() -> Note::read(). A similar thing happens while displaying elements, in the scanElements() function which is also defined repeatedly in each class.
After this refactoring, we would be able to do the same in a single function, somewhat like the following:

void Writer::writeElement(Element el) {
    beginElement(el.name());
    for (p : el.properties())
        writeProperty(p);
    for (c : el.children())
        writeElement(c);
    endElement();
}

To do this we would also have to store the properties of each object in some way so that we can iterate over them. I was earlier thinking of creating a macro called PROPERTY which we can use as shown ahead, which would allow us to iterate over the properties. (It could store them in a vector called _properties defined in Element or ScoreElement).

class Note {
    …
    PROPERTY (int, pitch);
    PROPERTY (bool, hidden);}

I found out while reading the code that there is also an already existing system used to store properties, which makes use of the QVariant class, and we can actually iterate over the Pid enum and maybe that could mean that we'd not have to use macros at all. But I'm not sure whether it will work or not. I also found out that QT has its own introspection system (also based on macros). We could use that one too, but that would make the whole code difficult to move to another framework if we choose to do so in the future.

There is even a GUI debugger available in the debug builds which shows the element hierarchy, as shown. However, I checked the code of this debugger to see how it finds the children and found that here too it is hardcoded.

debugger.png

Work I did

After exploring the existing code a bit, I tried to make a debugger that dynamically gets the children at runtime and displays them. I made a basic implementation of the children vector for experimentation, added a "debug_tree" action to the debug menu, and made it print the tree. I couldn't make a GUI yet, but I'll do that too soon. Here is the experimental code on github. Anyways here's what I got so far: I tried to add three notes and a major triad and ran debug_tree and here's the output I got from the console.


...Ms::MuseScore::cmd: TREE DEBUG
...Ms::debugTree: --> Score ( 0x55bfd5ab6840 ) -> [ 12 children]
...Ms::debugTree: ----> Lasso ( 0x55bfd5af3b40 ) -> [ 0 children]
...Ms::debugTree: ----> Note ( 0x55bfdbf9fd60 ) -> [ 0 children]
...Ms::debugTree: ----> Chord ( 0x55bfdbfb8790 ) -> [ 2 children]
...Ms::debugTree: ------> Note ( 0x55bfdbf9fd60 ) -> [ 0 children]
...Ms::debugTree: ------> Stem ( 0x55bfdc07b0b0 ) -> [ 0 children]
...Ms::debugTree: ----> Chord ( 0x55bfdbfd1830 ) -> [ 2 children]
...Ms::debugTree: ------> Stem ( 0x55bfdbfd2490 ) -> [ 0 children]
...Ms::debugTree: ------> Note ( 0x55bfdc0e5300 ) -> [ 0 children]
...Ms::debugTree: ----> Rest ( 0x55bfdc01bff0 ) -> [ 0 children]
...Ms::debugTree: ----> Note ( 0x55bfdc025ff0 ) -> [ 0 children]
...Ms::debugTree: ----> Rest ( 0x55bfdc038cf0 ) -> [ 0 children]
...Ms::debugTree: ----> Note ( 0x55bfdc0aefd0 ) -> [ 0 children]
...Ms::debugTree: ----> Chord ( 0x55bfdc0d6110 ) -> [ 5 children]
...Ms::debugTree: ------> Note ( 0x55bfdbfae8e0 ) -> [ 0 children]
...Ms::debugTree: ------> Note ( 0x55bfdc0aefd0 ) -> [ 0 children]
...Ms::debugTree: ------> invalid ( 0x55bfdc0eff70 ) -> [ 18446744069414584335 children]
[1] 13941 segmentation fault (core dumped) build.debug/main/mscore

It's not working as well as I thought it would! It didn't even show the pages and measures, and it even crashed showing some invalid elements, but I'm working on debugging it and investigating these issues.

Questions

There are still some questions that I need to discuss during the community bonding period.

  1. One of them is regarding the properties system, as I mentioned above. Should we go with C++ macros, or use Qt's introspection system, or not use macros at all?
  2. Secondly, If you look at the first UML diagram carefully, you would notice that there are actually many ways in which we could arrange the hierarchy. For example, there is a way to go from MasterScore to Page to System to measure, but there is also a way to go from Score to Measure directly. Should Pages be part of the hierarchy or should they remain outside it, etc? So we will have to decide what hierarchy should exactly be followed.
  3. Lastly, We will also have to decide what to do with the spanner elements like ties and lines. How will they be represented in the tree model?

I welcome the community’s feedback and suggestions on these questions.

At last, I would like to say that I am extremely grateful to MuseScore and Google for providing me with this opportunity. I would put in my best effort to make this project successful!

References

Link to my GitHub: https://github.com/Kartikay26
Link to my project proposal: Tree Model GSoC Proposal Draft
Next Blog: https://musescore.org/en/user/1743616/blog/2020/06/02/week-0-start-codi…


Comments

  1. To me it sounds like a good idea to reuse and expand the Pid system if you can.
  2. I think the children of the score should be the measures. It would also be good to be able to access pages from within the hierarchy, but it doesn't need to be via parent() and child().

I would recommend to collect the "simple" problems that are pending for many years and find out which ones of them are caused by the choice of that structure.
That knowledge should lead the direction of work on the structure rather than pure IT design.
E.g.
-why is introducing some kind of triplets so cumbersome in some circumstances?
-why is "full" copy(or cut)/paste so difficult that it has never been implemented?
-why is local time signature so error prone?
-why is it difficult to limit recompute of layout in big score?
-...
Some of these problems have no link with the structure of the code, but some of them can really help to understand why some decisions taken more than 10 years ago are perhaps not the best ones in the future