SVG export (once again!)

• May 23, 2015 - 16:46

Hi, men!

I have been wondering for quite a while about the very reason why the svg files exported by "MuseScore 2" are MUCH bigger then the ones exported by "MuseScore 1.3", so I decided to open one of such files and have a look at the svg code.

Well, I discovered that MS2 doesn't do any reference to the mscore font anymore, replicating a whole svg path for each and every musical symbol. I can easily understand that the choice to insert the path of symbols instead of referencing an external font leads to a major advance -- there's no need to install the font in the system. Looks like good practice to my eyes, but wouldn't it be MUCH more efficient to insert the needed paths between <defs></defs> tags and write them just once, and then use <use> tags when needed?

Moreover, why does MS include each and every svg object between <g></g> tags? Wouldn't it be more efficient grouping objects by family rather than isolating each and every object in its own group? Where's the point?

I noticed another thing for which I can't see any reason: MANY lines of code represent, literally, NOTHING. Gosh! That looks quite insane, doesn't it?

Oh, please take my words as constructing ones -- I'm just trying to give my two-cents to help in improving your already EXCELLENT software (I use it a lot and I LOVE it as-is but, you know, even perfection is often perfectible).


Comments

Would it be a difficult task implementing svg export from scratch? After all, svg files are just text files, and I'm rather sure that the structures and classes that MS uses internally to manage its symbols on screen and on paper can be easily converted to proper svg code, provided that one knows the needed details. Am I wrong?

What's the file in the source code where MS does its svg magics? I would like to have a look and see if I can grasp anything in there (probably I'm not going to, but--you know-- curiosity is some sort of a desease I suffer from).

EDIT: Never mind, looks like I found the answer by myself -- there's not such a place, as (as someone already wrote here too) MS relies on QT for its svg export.

In reply to by Aldo

Well, as you'll see, MuseScore really doesn't deal with SVG format at all. It's just a black box like PNG or PDF or direct printing or screen rendering. The "magic" is all done by Qt via the QPainter class. What MuseScore does is render graphic primitives like lines and curves (for staff lines, bar lines, stems, slurs, ties, etc) or characters from a font (noteheads, accidentals, clefs, time signatures, articulations - really just about everything that is not a simple line or curve) into a QPainter object, then ask Qt to deal with it from there. Qt then takes that QPainter object and outputs to the different sort of file formats or devices we need to support.

The place where MuseScore says, "take this QPainter object, fill it up with a bunch of line drawing primitives and characters from fonts, then write it to an SVG file" is here:

https://github.com/musescore/MuseScore/blob/93d1b4893d4e6df06b4788854de…

The actual filling up of the QPainter object happens in paintElement(), which is the exact same function used to fill up QPainter objects for PNG export. And it's the exact same basic process used to print directly, display to screen, or write to PDF.

Ah! Interesting page indeed, though a bit intimidating to my eyes (2600+ lines of code in a single file!). I noticed this line:

QList<const Element*> pel = page->elements();

Well, I have no knowledge at all about the QT library, but I suppose that the statement at line 2588 says something like "take each item/symbol in this page and put it into some sort of array/list that QT can manage". Then, in the few next lines, MS asks QT to sort those items and compile the svg code based on such list. The same process is repeated for each page of the score, translating the symbols by the width of the page so that, in the end, the svg file shows all of the pages side by side in the same svg image.

I can see a quick way to improve the code and make it more consistent with what MS does when exporting png files: save multiple numbered files, each containing a single page of the score (I guess that "recycling" the code already used in savePng() could be done in a very short time).

Apart from that, as you have been so kind to point me to the page you pointed me to, could you please tell me where I can get more information about the list of the elements contained in a page and its/their format? I'm still quite convinced that converting those symbols in better svg code should not be too difficult -- tedious, for sure, but not too difficult. I really would like to have a look. Probably, the trick could be taking the list to a subroutine, analize it a bit to get some information about its elements (type, scaling, positioning and so on), and provide a cycle that translates each symbol to an easier, smaller (that is, less verbose) and more readable svg code, then save the resulting text file, bypassing QT. Correct?

Edit: I didn't notice the fact (that you suggested--sorry) that paintElements() is called and that paintElements() is not a QT function but a MS one. Things are partially different from what I tried to guess, maybe simpler. Looks like the key could be in the Element class (provided that it is a class and not a structure or who-knows-what). Where can I find information about Element?

Many, many thanks indeed. That's exactly what I wanted to read. Again, there is plenty of code, so things look not as easy as I thought. Anyway, I'm going to (try to) grasp the contents of those file.

I've recently been animating svg sheet music from javascript, using svg files that MuseScore 2 generates. I am not familiar with v1 svg export, only v2.

From the animation point of view, instances of <use> elements are not available via the DOM. IMO this is an oversight, but there's not much I can do about it. I agree with your intentions, but this is not exactly the hottest item in the browser developers' agendas, and I'm not holding my breath. So please don't make this code "more efficient" via <use>.

I'm mostly glad that so much of svg, webvtt, and html5 is actually implemented in all the major browsers now. It may be a bit clumsy in the moment, but it works and it's 100% pure structured text. It almost makes me miss working with awk and sed...

In reply to by sideways

It would be awesome if you could share some of the knowhow you built while animating SVG with javascript. We (MuseScore developers) talked about making a visual index for MuseScore several times and SVG + Javascript would be perfect. A visual index would be a piece of sheet music specifically made to contain a lot of different musical elements and links to relevant parts of the handbook for each elements. Something like http://www.finalemusic.com/UserManuals/Finale2012Mac/Content/Finale/vis… but for MuseScore and without Flash...

In reply to by Nicolas

I was going to get around to sharing once my project was more complete, but here goes:

I started by animating small snippets, max: 4 bars 2 staves, and synchronizing them with audio files. I created a set of custom SVG attributes ("data-attribute_name") to store note duration, meter, highlight color, etc., and manually applied them to small SVG files exported by MuseScore. I then used JavaScript setTimeout() to setup the animation totally in advance of audio playback. The only hack is that audio playback in HTML5 has a delay before it begins, and I ended up hardcoding a 42 millisecond delay before starting my animation, in order to sync with my .mp3 files.

Now I need to animate larger pieces of music, so I also need 2 things:
1. A way to automate the markup process
2. Integration with the other SVG files that I'm animating to the same score via WebVTT

WebVTT and the HTML <track> element allow me to do all my animation from a single event handler: myTrack.oncuechange(). They also eliminate my 42ms hack.

On the other hand, automating the SVG markup and the WebVTT cue creation is staring me in the face, and it's not looking pretty unless I can get inside MuseScore or create some pretty amazing string parsing. So I'm looking into the MuseScore code for exporting SVG, and here I am.

I don't know if I understand your Finale example, but I think that custom attributes (with the "data-" prefix, as prescribed by W3C) are your solution. Simply add these attributes to the SVG elements you want to link-up. SVG can fire user events like click and hover, if you set it up right.

Maybe you can help me get inside the MuseScore SVG export code. I get stuck on this line of code:
{syntaxhighlighter SPEC}Q_D(SvgGenerator);{/syntaxhighlighter}

I can't find Q_D() anywhere. I can't find any calls to any SvgPaintEngine methods, especially:
{syntaxhighlighter SPEC}qpenToSvg()
qbrushToSvg()
qfontToSvg()
drawPath(){/syntaxhighlighter}

Where does this happen? It's not in MuseScore.saveSvg() in file.cpp. That's the other place I don't fully understand ...yet. It seems that this is where the Element class you were discussing above comes into play. I'm still digging on my own, but your guidance would be greatly appreciated. I haven't coded C++ in over a decade and I ended by coding C++ templates, which is a complete mind bend, so my skills are rusty.

Thanks,

Sideways

In reply to by Nicolas

FWIW, I am impressed by what people who write ABC editors have been able to accomplish with this. I haven't used it in a while, but I believe EasyABC shows you an SVG of your score in one window as you type the code in another, and if you then click a note in the SVG, the editor window is automatically positioned to that spot. I guess that is using Jef Moine's abc2svg, which is itself based on his amazing abcm2ps. But this is all new since I really was seriously doing anything with ABC, so I'm not really up on the details.

In reply to by sideways

Mmm... so you do think that using <use> would not be a good solution. Ok. What about defining a SVG font once in the file and using <text>, instead? That way, multiple replications of the same path data over and over could be efficiently avoided, couldn't them?

In reply to by Aldo

<use> elements would certainly reduced the size of the .svg files; my problem with them is that they are not animatable, not available via the DOM. For staff lines, clefs, time signatures, etc. that would be fine with me, I want to animate note heads, dots, accidentals and possibly some other elements.

I'm not sure about your font idea, as <text> elements in SVG have a whole slew of formatting and spacing options that might throw your X/Y coordinates off. They are positioned relative to a baseline, unlike <path> elements.

After my post yesterday, I re-discovered 2-3 possible inefficiencies in the SVG exporter:

1. For every note head there are two empty <g> elements, one above and one below it. They seem completely unnecessary and bloat the file significantly. By "empty" I mean that there are no other elements inside the group, but the group itself has a full set of attributes specified.

2. In spite of not needing fonts for notes anymore, all the font attributes are still specified in the SVG. This is not as big a bloat as the first item, but it's still significant, as it occurs in every group element.

3. I'm not sure about this one, but I wonder why everything is enclosed in a group (<g> element). Is that really necessary?

Here is an example:
{syntaxhighlighter SPEC}

{/syntaxhighlighter}

In reply to by sideways

There is one important concept that needs to be understood regarding graphical export in MuseScore. There is currently NO link between the score data and the graphical data.
Why? Because it's the way Qt works. MuseScore uses QPainter to draw scores (drawLine, drawText, drawPixmap etc...) and MuseScore has no idea *where* the drawing will happen. It could be a Widget (the score view you operate on), a PDF, a printer, an SVG file, a PNG etc... These are called QPaintDevices.

Most of them are internal to Qt, the SVGGenerator is the only we have in MuseScore because we needed to patch it to export font glyphs as outline and not text.

In any case, the QPainter and QPaintDevice are loosely coupled and there is no standard way to add more data in this relationship...

Thanks for sharing your workflow. I was at more or less the same point. Using snapsvg and css selector in order to select the clef for example... which is not workable in the long run. The clef shape should be labeled as such in the SVG.

In reply to by Nicolas

Wow, that's a bummer. There's no way to link a table of data in there at all? Or to know what kind of object (or Element class instance) that you're operating on inside SVGGenerator? Well, I guess I'm back to intense string parsing, reverse-engineering the QT output...

Do you realize that the <path> elements generated by QT are consistent, and it's the transform that positions them? So, you can identify a note head by the <path> contents, for example. Rests and stem flags too. Beamed notes present a problem because the beam is shared by more than one note, and stems are obviously various lengths, but always the same width and always a vertical line.

Is there a way that this string parsing could be done inside SVGGenerator and text could be added there? Clefs are going to be the same <path> everywhere too, just the transform attribute in the containing group is different (the location).

In reply to by sideways

OK that's clever. You wouldn't need string parsing in SVGGenerator, you could compare QPainterPath in but you will face other problems like different fonts used for the clef.

A very ugly way, since the svg rendering is linear... we could have a global variable somewhere saying "hey we are rendering a clef, or even clef number X", set this variable in the Element::draw() and implementation... and check this variable in SVGGenerator...

In reply to by Nicolas

I thought fonts weren't an issue. Why is a different font for the clef an issue?

Element is a class in C++, can't you put a "type" member in there somewhere and expose it via a property? "type" could be Clef, Whole, Half, Quarter, etc, a set of enumerated constants. OK, I just looked at the SVGGenerator::drawPath() code again and see that there is a "type" member in the Element class that is used for something else. So call the new member "glyphType" or some such thing.

Yes, the SVG generation is linear, and in reverse order (as far as notes are concerned). Yet it's not completely linear relative to the music timeline, it's linear by element type - rests all together, notes all together, etc.

It would be great for my purposes if it was totally linear (forwards or backwards), not by element type. Ideally I could create a WebVTT generator inside MuseScore and it could be linked to SVG via element id attributes. MuseScore knows the tempo, and it knows the note values, translating that to milliseconds is not difficult, I already have all the formulas :-) Dotted notes are an obvious issue, as the dot is a separate element, but I'm sure there's a way to work with that.

FYI - my debug method, by which I have so far learned all I now know about MuseScore SVG, is to color various elements in the score and then search for the hex RGB codes for those colors in the resulting SVG. It works well.

In reply to by sideways

sorry linear was not the good word. I mean it's not multithreaded. As you discover, the problem is not about having a type for the element but to get the element when it is drawn in svg generator. By design the SVG generator doesn't know which element it's currently drawn. My proposed hack was to modify the Clef::draw() function to set a static variable (QSVGGenerator::drawnElement for exemple, a QString) and use it to label the g or the path in svg.

In reply to by Nicolas

So the QPainterPath::Element in this line of code is not the same kind of Element class?

{syntaxhighlighter SPEC}const QPainterPath::Element &e = p.elementAt(i);{/syntaxhighlighter}

How many different Element classes are we dealing with here?

fyi - I'm currently downloading Qt so I can understand this better and waste less of your time :-)

In reply to by Nicolas

Let's see if I understand this now:

- Each sub-class of class Element, for example Clef or Note, implements its own draw() method.

- In file.cpp, the function saveSVG() iterates over all the pages in the score, calling paintElements() with the list of Elements for the page.

- paintElements() iterates over all the Elements in the page, and calls each Element's draw() method, passing the SVGGenerator's drawing function as a pointer.

Thus, each Element sub-class is calling SVGGenerator functions, like drawPath(). To make the kind of changes we are discussing would require changes to each Element sub-class's draw() method. My C++ is rusty, but I would hope that by adding a parameter into the SVGGenerator drawing functions we wouldn't have to then add that same parameter to all the other functions passed in as pointers to Clef::draw(), Note::draw() and all the other Element sub-classes' draw methods.

Am I understanding the flow here now?

...and if you're going to set a global variable, why not a pointer to the Element itself? That way you could manage the SVG strings as properties of the Element sub-class. It's not such a huge hack to have a global variable indicating the "currently printing" element. I would prefer to pass a pointer into the SVGGenerator drawing function, but the more I think about it the more I imagine needing to make too many edits in too many source files.

...now that I think about it, if you are setting a global pointer to the Element, then you could do it inside file.cpp, inside paintElements(). You wouldn't have to modify any other Element sub-classes or source files (except to create the SVG strings inside the sub-classes).

In reply to by sideways

That's correct.

Not sure the global element makes sense for every file format. So for a start you could copy paste paintElements in saveSVG and set a static variable in SVGGenerator in this loop. You can get the type of an element in a string with Element::name(e->type());. You might also want to count them to create IDs like Note-1, Note-2 etc...

You seem to know the SVG format better, feel free to make any change to make the export more efficient !

In reply to by Nicolas

Sounds good. For your needs that makes perfect sense. For me, I also have to make sure that the ID numbers are in playback order, or at least tied to playback order, so that rests and notes are grouped in the id assignment process. So I need to get into the playback functionality and create a loop that simulates the playback order of notes/rests.

Any insights you can provide about iterating over the elements in playback order?

I'll be digging around on my own in the mean time. Thanks for all the info and insight.

In reply to by Nicolas

I'm animating sheet music and synchronizing it to an audio file. I don't need any of the fancy stuff like repeats and codas, but if it's easier to include them, then fine.
So the notes and rests need to be in page order (top-to-bottom, left-to-right), or playback order.

I see that measures are ordered. Is that the way to get into this?

In reply to by Nicolas

OK, we'll call it measure order. So within each measure I can find the notes and rests and deal with them in left-to-right order? I also see that score.cpp has a nextNote() method.

And then there's the duration. I do see duration.h, but I'm still not connecting all the dots. Though I haven't been digging in MuseScore the past few days, I've been busy with other tasks.

And then there are chords and segments. Chords I am starting to understand, and segments seem to be the way MuseScore highlights during its own playback. These both seem to be key to what I am trying to accomplish.

So, I need to get the notes (chords) and rests in "measure" order and know their duration. I can calculate my WebVTT numbers in milliseconds based on that, though if it's simpler to get all that timing info from MuseScore I'll get it from MuseScore. In other words, I might actually benefit from understanding "playback" order.

So I'm attempting to not only add id tags to notes and rests in the .svg file, but to create a corresponding .vtt file that links those ids to timing information. Once I can iterate over notes/chords/rests in the proper order, the content of the code should not be particularly complex. WebVTT is an extremely simple format within this context.

Thanks.

In reply to by sideways

Segments are in what you call playback order. A segment contains all the chords and rests at a given point in time (tick). A chord contains one or more notes. A chord or a rest have durations. Notes are part of chords so, they inherits from the duration of the chord.

In reply to by sideways

I'm looking at SvgPaintEngine::updateState in svggenerator.cpp (line 976). It seems that this would be the place to eliminate the two extra empty <g> elements that surround each <g> element that contains a <path> element. I imagine that some Element property might be validated here and if it's not going to draw a path, then skip the creation of the <g> element altogether. This also seems to be the place where the decision to add font attributes to the SVG is made, so that efficiency in file size/content might be achieved in a similar fashion as well.

Damn, we've really overloaded the word "element", haven't we?

Any thoughts?

fyi - I have now verified in some simple test cases that these group elements and font attributes are not necessary for rendering the SVG. They are truly excess. I have attached a pair of sample files: MS-orig.svg is the original generated by MS. MS-small.svg is edited by me to eliminate all the empty groups and font attributes, as well as removing the group elements enclosing path elements. The file size in this extremely simple example is >30% smaller with the edits. Not bad. Further optimization could be achieved with the polyline elements. This example does not cover text, such as dynamics or chords, and is limited in many ways. The -small.svg file also has comments by me illustrating the various score elements in the SVG. All it really illustrates is the order in which the score internally stores the Elements, which is right-to-left, bottom-to-top on the page, and grouped by element sub-class.

Attachment Size
MS-orig.svg 44.48 KB
MS-small.svg 30.74 KB

Thanks for all the hints here! We needed to add an id attribute to the note elements. A quick hack was to modify ScoreFont::draw(), the block under if (MScore::pdfPrinting). Adding an id attribute to font, then we modified qfontToSvg() inside svggenerator.cpp. This worked fine, and we cleaned up that function as in the current svg format there is no need to add the font attributes. Please let me know if you need that, we could send a pull request that cleans up the exported svg to some degree.

Re: Gradients and Color Opacity in SVG
I see plenty of code for dealing with gradients and various flavors of color opacity in SvgGenerator.cpp, but I don't see anywhere in MuseScore that allows the user to create gradients or set opacity.

Am I missing out on features in MuseScore, or is this code unnecessary?

This forum topic seemed like the place to post this question.

In reply to by Nicolas

Thanks, that is what I thought. I saw the Alpha feature after I posted, though I imagine it is seldom used. I was more concerned with the gradient-related code because there is a relatively large amount of it. I want to remove it in my copy of the file.

FYI - I have successfully modified the mscore/file.cpp and libmscore/svggenerator.cpp files, and I am building SVG files with three features that might interest others:

1) For each <path> and <polyline> element: an attribute indicating the element type, the value is an Element::Type cast as an integer. This is, as I understood it, what you were looking for, lasconic, in one of your early posts in this forum thread. With this value you can lookup the MuseScore element-type name or any other properties you want. I added it because I need to tell the difference between two types of Element::Type::BAR_LINE, System vs. Measure, but it has general-purpose potential. An example:
{syntaxhighlighter SPEC}{/syntaxhighlighter}
The "data-" prefix is a recommended practice for custom SVG attributes. I achieved this functionality by adding a function SvgGenerator::setElement(const Element* e). This sets a variable that the code can use when writing the SVG file.

2) In terms of file-size reduction this is neither a significant gain or loss, relative to the pull-request code by Heslil; but in terms of file readability it is a big improvement: I have completely removed the group element wrappers (<g>) around everything. Each path and polyline element is separated by a newline and that's it. With the "data-type" as the first attribute specified you can see the element types sorted in Element::Type enumeration order.
The downside here would be the excess text in cases where a group element actually wraps multiple paths or polylines. The best example of this is Staff Lines, at the top of every page in the SVG file. But this is offset by the overall reduction of group elements and newlines. This change also makes the C++ code noticeably simpler. Others may disagree with the readability improvement, but it works perfectly for me.

3) If an attribute value is the same as the SVG default value, omit that attribute from the element in the file. This reduces the file size for any score where the notation black, for example, because the default for the SVG "fill" attribute is black. This also improves file readability, IMO.

I've attached two sample files, one created with MuseScore 2.0.2 and the other created with my code, adapted from the Heslil pull request. You can ignore the "data-chord" attribute, that's specific to my project. Let me know if you'd like to integrate any of those three features into MuseScore.

Attachment Size
SVG_Playback_04.orig_.svg 129.21 KB
SVG_Playback_04.svg 88.09 KB

@sideways, you've done a great job so far! If you have time, there are a couple of other issues with SVGs in MuseScore and it would be great if you could address either (or both) of these:

  1. Export of individual pages to separate SVGs - multi-page scores are currently exported into a single SVG file. It would be great to have an option to export them to separate files, and maybe only certain pages. This could potentially be done via File -> Print rather than File -> Export.
  2. Identifying pages within the SVG file - when more than one page is in a single SVG it would be very useful if content could be grouped by page. The SVG 1.2 spec introduces a <pageSet> element but this is not widely supported yet so something like <g id="page1"> would be safer for now.

I imagine (2) is not too dissimilar to what you are already doing and is probably quite easy for you to do. (1) may be a bit more tricky and would involve changes to the GUI so don't feel like you have to do it.

In reply to by shoogle

Glad you approve!

Both are relatively easy, as the code iterates over the score's visible elements by page. The resulting SVG file already lists the elements by page, it just doesn't use a group element to wrap each page's elements. As you can probably tell from my previous post, I am not fond of group elements at this time, so a "data-page" attribute for each path and polyline appeals to me more.

But the real issue for me is that these features do not interest me personally at all. As far as I know today, you are the only person interested in integrating my code into MuseScore. Until there is some real interest in integrating my code, it's hard for me to express interest in adding features that are purely for others in the community. I can certainly post my code to github and others can have at it.

As a workaround for #1 today, you could simply delete the excess pages from the full-score svg file. Each page begins with the staff lines, type 5, or possibly a slur segment, type 4 - the lower the "data-type" value, the higher it is in the draw/paint order. So it's simple to identify the pages within the file today. Even in release v2.0.2, it's easy to spot the staff lines because they are bunches of groups, each with 5 polylines inside.

In reply to by sideways

Well, I'm happy to integrate your code in MuseScore too. However, as I explained somewhere else, it would be good to have WebVTT support not mixed with SVG.

Regarding the multipage thing, I'm not a fan of having an option and even less of having anything related to SVG in print dialog. However, I'm ok to have SVG working just like PNGs if we believe it's way to go.

In reply to by Nicolas

WebVTT would definitely be left out of any integration at this time. Only the three features I described in my previous post are being considered here. I don't want to muck up general SVG exporting in MuseScore with my own ongoing development efforts.

So you want all three of the features?

If you can agree on using a "data-page" attribute instead of group wrappers, I can make that part of the integration. The more I sit with that one the easier it looks.

In svggenerator.cpp I would also recommend removing all the gradient-related code, but I don't know if you have any plans for it in the future. And also all the font-related code, which is clearly obsolete at this point.

Please excuse my newness to the integration process here. I've never contributed to an open source project this way, and I'm used to a strict integration/feature control process in the corporate world, where people listen to my opinion, but I don't have a vote.

So how does it work here? Who decides what? How do I determine what to integrate?

ps - To correct my previous post: the 3 files affected by these changes are mscore/file.cpp, mscore/svggenerator.cpp, and mscore/svggenerator.h.

In reply to by sideways

Short version: https://musescore.org/en/developers-handbook/your-first-submission
In practice, we work with Github pull request: https://musescore.org/en/developers-handbook/git-workflow

So you do a pull request with your new feature or your bug fix. The core developers review the code (I'm one of the core developers). Then we discuss the code with everyone and it gets modified if needed an in the end merged (or not).

About this changes specifically, I would like to keep the gradient and the font code for now if you don't mind. Feel free to group the fonctions in one place and put a comment that they are currently not used.

Regarding the feature, 1/ data-type and 3/ default values are fine.
Regarding 2/, I guess g could be useful to group element semantically like stem+hook + noteheads if one would like to animate them as a whole. Currently most of the g seems indeed redundant, so sure go for it.

To sum it up, feel free to make a pull request with these 3 changes.

In reply to by Nicolas

Thanks for the info. I'll create versions of those 3 files with only those 3 features and make a pull request. I'll post the pull request info here when I have it.

Regarding group elements: Animation is specifically what I am doing, and I have found it simpler to group by common attribute value than by using a group wrapper. The SVG DOM allows css-style selectors in querySelectorAll() and functions like that. It's very straightforward on the animation side, the svg file is easier to read (a lot fewer newlines), and the group wrappers are a noticeable complication for the MuseScore code in C++. Also remember that animating a score means animating multiple notes across multiple staves simultaneously, not just a single note.
Short version: don't worry, be happy.

What about the "data-page" attribute? Let me know over the next day or two. I'm in the middle of some other tasks and it will take me a day or two to prepare the pull request.

In reply to by Nicolas

Currently MuseScore stores all the pages in one SVG file. Even the proposal to create files with a subset of pages assumed it would be an option, not that MuseScore would always create a separate file per page. I think you and I and shoogle all agree that his feature (1) is off the table for now. I have used a program that outputs a separate file per page, Scribus, and I found it annoying, but it is an alternate solution if you want a page-based display.

To be clear: There is no concept of pages in the current implementations of SVG. Paging in MuseScore is handled by moving the current location offset (transform matrix) horizontally the physical width of a page. The SVG document is one big canvas with these pages of systems laid out horizontally, separated by margins. If you want to display SVG as pages you have to do some extra work.

The "data-page" attribute would allow readers of the file, human or software, to group display elements by page, and thus manage a page-based view. You might place a "scroll forward one page" button on your HTML page and have it control the SVG display. For example. Maybe shoogle can provide some details as to how he plans to use it. I have no plans for it, personally.

I would add the "data-page" attribute to the elements in the file only if a score has more than one page. No need to add clutter to single page documents. I plan to have relatively large single page documents, for example, in a piano-roll style of scrolling: by chord/rest, by measure, or by system.

In reply to by sideways

1. I'm not sure that we are all agreed (1) is off the table. As Lasconic said, all other image types export one page per file. Another reason is to fully support using the -T command line option to trim the border, necessary for inserting small music examples into word processor documents. However, you don't have to implement that if you don't want to.

2. If you don't want (1), what I want with (2) is a way to quickly distinguish which content belongs to each page so that I can delete all the other pages quickly, possibly via a script, and thus achieve (1) manually myself. I'm not familiar with SVG data-page, and I can't find much information about it online, but I have no preference between data-page vs groups if both do what I want. I'll trust your judgement here. The ability to manually control the displayed page via HTML sounds intriguing, but could of course be done using separate images too.

3. What I meant about the print dialog is that we would have "Print to File" as an available printer, and then a tickbox option to choose between PDF and SVG (and potentially EPS). This is standard on Ubuntu, and means that we could take advantage of the existing GUI elements for choosing a page selection.

4. Long term, perhaps what we need is to convert from MuseScore's internal format to some kind of intermediate format (possibly SVG) and then from this format into the desired output format (PDF, EPS, HTML, etc.) using exiting libraries. Using an intermediate format might allow us to preserve fonts and the information about the different elements (i.e. this is a treble clef, etc) rather than using Qt to go straight from the internal format to the output.

So to summarise:
(1) we need to vote on (but basically you can implement whatever you like since you are doing the work).
(2) should be a no-brainer. It barely adds to the file size and it isn't hard for you to do.
(3) needs discussion.
(4) probably a discussion for another time.

In reply to by shoogle

OK, I thought your feature (1) was with selectable page ranges, etc.

As far as producing separate files per page, if there's a consensus that it's the way to go, then it's not hard to do.

If all you want is a manual way to separate pages, then the data-type attribute will make it easy enough. Every time it cycles back to type 4 or 5 it's starting a new page. But the problem there will be that this page will still be offset horizontally, unless it's page 1. If what you want is really a separate page per file, then you need to eliminate the offset/transform process. I thought you were looking to automate something in terms of a paging interface, my bad assumption. Really, a custom data-page attribute will not help you because of the horizontal position of pages 2+. Even if you implemented it inside a group element you'd have to deal with that manually too, though possibly in fewer places.

If you really want to always output a separate SVG file per page it won't bother me, so long as the file name for a single-page document doesn't always have a "-page1" in it. But for multi-page documents you'd have to implement a scheme like Scribus, where each file name has a page number built-in to it.

But why do you want a separate svg file per page? What advantage/use does that have?
Why are you exporting to SVG? How are you using the "images"? Are they snippets, one per page, like an icon library? Are they full scores formatted in pages?

I have a specific use for SVG, but I don't necessarily understand why others would export to SVG from MuseScore.

In reply to by sideways

I'd like to use the images inside LibreOffice (or similar program) to combine multiple songs into a full score for an opera or musical. LibreOffice is obviously much better suited for adding text than MuseScore, and this way I can do things like have a contents page and automatic page numbering. I really need one page per SVG for this to work. (2) was simply another way to achieve (1) that I thought might be easier for you to code, but if it wouldn't work then I really need (1).

So if you are happy with implementing (1) (i.e. one page per SVG) then I don't think there will be any objections because this will make SVG export consistent with PNG export. Afterwards, you'd need to test the output of:

mscore filename.mscz -o filename.svg -T

to make sure that the images are properly trimmed. The trimming is already implemented in the code, but currently only works for single-page scores in the case of SVG export. It's possible that moving to one page per file will fix this automatically since it works properly for PNG export already.

As for the naming convention, MuseScore's PNG export does it like this:

Exporting "filename.mscz" to PNG:
1 page score output name
filename-1.png
1-9 page score
filename-1.png, filename-2.png ... filename-X.png
10-99 page score
filename-01.png, filename-02.png ... filename-10.png, filename-11.png ... filename-XX.png

So unfortunately single page scores do get the "-1" ending, but this could be changed unless @lasconic or someone objects. You can probably borrow this naming code from the PNG export, wherever that is in the source.

In reply to by Nicolas

I think this means I should not add the custom "data-page" attribute. There is no clear use for it at this time and it will only add clutter. It's easily added later if anyone cares. And if MuseScore always outputs 1 file per page, it's superfluous.

As far as the single-page per file, it sounds like it would be best if lasconic or someone with more experience with that part of the code does that work.

I'll put together the pull request for the original 3 features I proposed.

Thanks!

In reply to by shoogle

I added issue #86686 to the issues db here at musescore.org: https://musescore.org/en/node/86686

I created pull request #2286 on GitHub: https://github.com/musescore/MuseScore/pull/2286

My first time really using GitHub, so I hope everything went according to spec. Also, again, as my C++ skills are rusty, I apologize in advance if I'm doing something outdated or otherwise bad practice. I'm imagining that the block of #defines in SvgPaintEngine is a possible candidate...

An informational post for those interested in MuseScore SVG Export:
It's topical because it relates to the opening post that launched this forum topic in terms of reducing the file size and the SVG <use> element.

Open the example file I posted above named sideways.svg in a text editor, then look at the polyline elements with data-type="5" towards the top of the file. These are staff lines. This score only has 1 system, with 2 sets of 5 staff lines. You would think that this would equate to 10 polyline elements in the file, one for each staff line (2x5). But the file has 20 polyline elements for the staff lines.

This is because each measure's staff lines are drawn separately. I cannot think of a case where a score would change the number of staff lines for a part in the middle of the score, but I'm not an authority on the subject. If it's simply a side effect of mixing Qt with the basic MuseScore design, then the SVG file could be simplified by drawing the staff lines only once per system. In larger scores with 8 or more measures per system, this adds up to a lot of polyline elements.

I'm not suggesting that anyone make these changes now or ever, I'm simply laying out an idea for simplifying the SVG file. Staff lines are an ideal candidate for SVG <use> elements: they are repeated throughout the score and are the least likely candidate for animation. Plus you could group all five staff lines into one definition, reducing each staff to 1 line of text in the SVG file instead of 5 (or however many staff lines that particular staff has). Ideal for the SVG file, but probably not so ideal for the code, so I won't be working on it anytime soon.

That's the final frontier in the optimization of the SVG file, IMO. Not much to optimize after that, though all kinds of features could be added for other purposes. And with the future functionality of one SVG file per page, multi-page scores may have a lot of files, but each file will be relatively small. So it's all headed in a positive direction at this point.

In reply to by sideways

We don't currently support changing the number of lines of a staff measure per measure, but it's definitely something that exists in the music notation litterature. Also, it's currently possible to have a invisible measure in the middle of a staff, and staff lines in MuseScore itself are drawn measure per measure. So, I don't think we can optimize this one.

In reply to by Nicolas

Thanks for the prompt response. It was all hypothetical anyway, to illustrate the point that the staff lines are drawn per measure, which equates to a lot of polyline elements at the top of each file. In case anyone ever wonders why there are so many.

Though in writing it up and giving it 10 minutes of thought, I see a way it could be done for SVG; not the <use> element, but drawing the staff lines only once per system, not once per measure:
file.cpp saveSVG() uses Page::elements(), which iterates over elements by system by measure. Adapting that code inside saveSVG() would allow you to find the left and right end points for the lines in the system and draw just one line. It's not as simple as just that, but it wouldn't be excessively difficult.

I think about this because my SVG files are for scores with one very large page. Think of a vocal part, for example: 100 measures, one long system, one long staff, 5 long staff lines. Right now that SVG file would have have 500 polyline elements for the staff lines. Add a piano part, that's 1,500 polylines instead of 15. With 10 parts, 10 staves per system, it's 5,000 vs. 50 polylines. And that's not a huge score, in length or number of parts, though I won't be making anything larger than that in the near future.

a side question, while I have your attention: Why are the elements returned by Page::elements() in right-to-left order on the page? Is there a reason? I know that there are languages which are written in right-to-left order, but music is not one of them :-)

In reply to by sideways

I just bumped into another frontier on the SVG front: a separate CSS file.

It may not be appropriate for general MuseScore, but it is a commonly used method for reducing the size of the SVG file and can provide a useful organizational/maintenance tool as well. For final, published SVG, it's good practice to use one. Interestingly enough, the way MuseScore organizes its own Text Styles provides a perfect framework for this. Combined with the "data-type" attribute (assuming that pull-request is pulled) it would be relatively simple to create CSS styles for each type of MuseScore element in a score.

My scores all have the same styling, so my version of MuseScore can avoid writing default CSS styling attributes to the SVG file, saving even more space, and providing consistent styling across the scores as well. To make that work in a general purpose version of MuseScore would require opening the CSS file and reading the values for each Element::Type, then avoid writing default values to the SVG file.

Something else to consider on the SVG front. I can see how some might not like the idea of 2 files instead of one, but when you've got 10 pages of score in 10 separate SVG files, adding 1 CSS file to that mix only makes it 11 files, not 20. If you're publishing a whole album of scores, you could reduce that to 1 CSS file for all of them. That's what I'm going to attempt to do.

The whole thing gets simpler if the score has zero deviations from the Text Style settings, but that's just wishful thinking.

In reply to by sideways

Sounds interesting. I can see this being very useful, but (as I think you guessed) it probably wouldn't be welcome in the MuseScore code because the vast majority of users will want standalone image files. I think most people would be very confused if their SVG stopped working due to a missing CSS file. It could potential make it into the code via a command line option or if it was buried deep somewhere in the settings where most people wouldn't see it. However, it may come in handy later on if we decide to use SVG as an intermediate format when converting to other output file formats.

In reply to by sideways

I am rewriting this a second time, because I now fully understand the transform attribute, which MuseScore is using to not only offset to a location, but to scale. In this case the scale is 1:3.125. Not sure where MuseScore gets that number, but I see it in my other scores too. It's either 3.125 or 3.105. Almost every single SVG element is scaled by one of those two factors.

I have succeeded, via manual text editing, in reducing the number of polyline elements of data-type="5", staff lines. If anyone else ever wants to do it, here is an example, using this score from MuseScore.org: https://musescore.com/user/4215981/scores/1161361#
The score is a string quartet with 20 staff lines, 4x5. I edited the score down to bars 26-158 (the fugue itself), removed all the "newlines", then exported to SVG.

Remember that everything is in right-to-left, bottom-to-top order. I'll be operating on the first measure's staff lines, which are the last 20 staff lines in the SVG file. The idea is to extend the width (x axis) of those lines all the way through the last measure. Here is the very last staff line polyline element in the file:

<polyline data-type="5" ... points="0,22.656 186.598,22.656"/>

That bold number is the width of the first measure's staff lines. We'll be replacing that number with a new number: 28774. Two more simple steps:
1) Delete all but the last 20 staff line polyline elements. In this case that's 2,640 lines of text = 20 lines by 133 measures minus 1 measure
2) Replace all instances (in those remaining 20 lines) of 186.598 with 28774

How did I come up with 28774? The rest of the numbers are in the very first staff line polyline element in the SVG file, one of the last measure's staff lines:

<polyline ... transform="matrix(3.125,0,0,3.125,89099.6,698.55)" points="0,0 288.129,0"/>

Those bold numbers are the horizontal offset and width of the last measure's staff lines. Add those two numbers to our original width (186.598) = 89574.327. Divide that number by the scaling factor in the transform attribute (3.125, x and y scaling) = 28663.785. Now that's 110.215 off from my target number, so it did require a small amount of trial and error. I'm still learning about stuff like transform=matrix(), which has 6 parameters. There is probably a more correct version of my math that is more precise.

Anyway, not too bad, once you get the hang of it. Keep in mind that this example is for a single system on a page. With a multi-system page you'd have to multiply the number of staff lines and locate the appropriate polyline elements in the file. Fortunately this process only deals with the x axis, so you'll still only be replacing one number, you'll just be replacing it more places.

Statistics (in case anyone but me cares):
14.5% reduction in the number of lines in the SVG file
5.3% reduction in file size
Final SVG file size = 8,003,584 bytes

That file might be considered large or small, depending on the context. Relative to a compressed MuseScore file? It's huge. Relative to a video media file that plays 4:27 of animated sheet music? It's tiny. Fortunately, it's text, which is very compression-friendly. I see 95.8% file size reduction with basic zip compression on this SVG score, which brings the file size down to 335,364 bytes. Still bigger than a compressed MuseScore file, but not much (at least not in absolute terms). I have not posted any sample files because of the multi-megabyte size. Though I suppose I could zip them up if anyone really wants to see them.

In reply to by sideways

[If I'm spewing too much info into this topic, just say so and I'll quiet down]
This is the line of code in file.cpp/saveSVG() that creates the 3.125:1 scaling:
{syntaxhighlighter SPEC}double mag = converterDpi / MScore::DPI;{/syntaxhighlighter}
converterDpi = 300; MScore::DPI = 96; 300 / 96 = 3.125;

This number is then used in every transform="matrix()" attribute setting in every element in the SVG file. There is one exception to this: a set of element types that use 3.105 instead of 3.125, e.g. note head and dots. I do not yet know why. The matrix data shows up in SvgPaintEngine::UpdateState() in the state argument received by the function from Qt. I figure it must be something inside that element itself that causes the scaling to adjust. Class Element does have it's own _mag variable, but that's set to 1 in all of these elements.

Two notes/questions on this scaling:
1) This is the kind of thing that could be in a group that wraps the entire document, essentially part of the header/footer:
{syntaxhighlighter SPEC}<g transform="scale(3.125)">{/syntaxhighlighter}
That way each element could use the simpler "translate()" instead of "matrix()":
{syntaxhighlighter SPEC}<path transform="translate(xOffset, yOffset)" ...{/syntaxhighlighter}
2) The difference between 3.125 and 3.105 is 0.6%, not necessarily visible. Is this necessary for reasons I don't yet understand? What is causing it to vary at all?

I've attached two small sample files. One with mixed 3.125/3.105 (144/84 instances), the other with all the .105's converted to .125. I have compared the two at 300% zoom in Firefox and I cannot see a difference. Chrome goes to 400%, but I don't think it will matter.

Of course the whole concept of internally scaling an entire SVG file is redundant. That's what the "S" in SVG stands for: "Scalable". It does seem to assist with the basic use of SVG as clip art, fixing a default size that matches the printed size. But that's a paper-based perspective. From the general SVG perspective, it's more confusing than useful. The image will scale to whatever frame you place around it.

I have now posted a 3rd and 4th file: They were generated by changing that line of code in saveSVG() to this:
{syntaxhighlighter SPEC}double mag = 1; //converterDpi / MScore::DPI;{/syntaxhighlighter}
This causes the scaling to be 1:1, no scaling at all. Then I replaced "matrix(a,b,c,d,e,f)" with "translate(e,f)" everywhere (114 instances). The file didn't get much smaller, but I think the processing required at render-time is less. I could be overly hopeful on that one. At least it's less clutter in the file, removing those same 4 comma-separated numbers everywhere.

If you want to compare the results of these four, open them in separate tabs in your browser, then tab between them. The 3rd file is scaled down, but if you zoom in 312.5% it will be identical to the others. In the 4th file I've removed the <svg> element's width and height attributes, and changed the viewBox to be the same as in the 3.125:1 file. I don't know why it moved vertically in the window, but the scaling is identical to the 3.125 file.
You'll notice that the 1to1 files also have a glitch with the line in the lyrics, the hyphen separator, aka LyricsLine. I'll be figuring that out shortly (hopefully).

Attachment Size
3.105.svg 78.23 KB
3.125.svg 78.22 KB
1to1.svg 76.92 KB
1to1_No_w_h.svg 76.89 KB

In reply to by sideways

I don't mind you keeping us updated, but be aware that everyone here gets an email notification every time you post something **AND** every time you edit your post too. If you want to cut down the number of posts or edits then there are a few things you could do:

1. Don't bother editing to correct typos. We don't care about typos and we make them all the time.

2. Write your posts but don't submit them until the following day so that you can come back to it fresh and see if you missed something you wanted to say.

3. Write in a Google Doc and just post a link here. Keep the Google doc up-to-date and we can follow along if we're interested, and even leave comments if you allow it. You could copy everything from the Google Doc into the forum in one go at a later date.

In reply to by Marc Sabatella

shoogle: Thanks for the suggestions. I didn't realize about the edit notifications, ouch!, but that's why I asked.

Marc: Interesting. I'll check it out. I just changed this additional line of code and now I can create files with zero scaling, and they look good. Zoomed-way-in I see subtle improvements over the original, scaled file. I'm attaching the final file in this suite. You'll see that the <svg> element's width/height are the scaled-up numbers, while the viewBox is scaled down.
I changed this:
{syntaxhighlighter SPEC}printer.setResolution(converterDpi);{/syntaxhighlighter}
to this:
{syntaxhighlighter SPEC}printer.setResolution(MScore::DPI);{/syntaxhighlighter}
in saveSVG().

Attachment Size
1to1_4real.svg 76.92 KB

In reply to by Nicolas

This is a great fix, but I don't see how it affects the issue I raise above:

Why are there two different scaling numbers for elements in MuseScore? And the second number varies depending on the score, something I haven't figured out; but I have two scores with two different alternate scaling numbers:

0.993 and 0.8496

Why are note head, dots, accidentals, and other elements scaled down slightly, very slightly from other elements? In my other score, with the 0.8496 scaling difference, it's visible:some note heads are misaligned relative to the stems (some, not all).

Unfortunately I'm new to git and haven't figured out how to update my local files with this fix, so I haven't been able to test the change against my findings... Again, apologies for my clumsiness: I'm a semi-retired programmer who's a bit out-of-date.

In reply to by Jojo-Schmitz

Thanks for the git info, but my problems relate more to uncommited changes github thinks I've made and file conflicts. Not sure what happened.

Also, to clarify: the visible glitches due to the 0.8496:1 scaling, the glitches occur when I change that to 1:1 scaling. The file generated by MuseScore looks fine, it just has two different scaling numbers.

In reply to by Jojo-Schmitz

I have succeeded in building a MuseScore.exe with the latest master files plus my pull request changes plus the scaling changes to file.cpp described above. The global set of changes for DPI=72 did not change this dual scaling factor situation. I've attached the sample file I generated. Note that the Element::Type enum has a new value at the 7 slot SYSTEM_DIVIDER. This increments all the data-type="N" values above 6 by 1. It also aligns the blocks of text with 10 inside the enum, which is a subtle, but helpful improvement.

Things to see in the attached file:
1) You can see the 0.9936 alternate scaling factor in these element types: 11(ACCIDENTAL), 13(NOTE), 14(CLEF), 17(TIMESIG), 18(REST), 27(HOOK) and 56(NOTEDOT).

2) But note that some of the stroke-width attributes on polyline elements are also affected. See lines 111-115 of the file, data-type="33", TUPLET. These are the lines drawn by the triplets in the score. They have stroke-width="0.49680", which is exactly half of 0.9936. I've seen other scores with stroke-width="0.9936". Re-scaled those would be stroke-width="0.5" and stroke-width="1".

A more complex score would reveal more element types affected by this. But even in more complex scores I've only seen 2 distinct scaling factors. Are these two separate fonts? What is happening here?

btw - Jojo, thanks for the git tips and your patience. I had to add a git reset to the mix to get the other commands to not throw errors, but even then, neither my local nor remote fork's files updated. I ended up downloading the zip file from github.com and testing it that way. I think I need to wait until my pull request is accepted or declined and then start over from scratch.

Attachment Size
2_Scales.svg 77.81 KB

In reply to by sideways

This increments all the data-type="N" values above 6 by 1. It also aligns the blocks of text with 10 inside the enum, which is a subtle, but helpful improvement.

Then maybe it would be better to use the actual element name and not a number in the class/data-type attribute. Ok it will make the svg bigger but as you explained it's just text and it's zippable. It will probably be zipped in most web oriented scenario.

In reply to by Nicolas

I had the same thought. Though when you make changes to an enum, shouldn't you only add to the end of it? That would also avoid the issue. Although I get the sense that there used to be another element somewhere early in the enum just because of the alignment to 10 situation, so maybe this is an undo of a previous delete?

Now that I remember it, this enum is also tied to the draw/paint order, so inserting items might be necessary at times. Yes, in that case the name might be more constant, no way around it.

But, as I mentioned in the pull request comments, both ways make sense to me, and it's your decision - the collective you, "vous" plural :-). I am thinking more and more in terms of CSS styling, so the "class" attribute is becoming more and more appealing to me, but there's an attribute=value CSS selector, so either way works on that end too.

In reply to by Nicolas

In an attempt to make your decision easier, here is a sample file using element names instead of numeric types. The file distinguishes between different types of bar lines, and not just SYSTEM vs. SEGMENT. The code uses BarLine::barLineTypeName(), the BarLine equivalent of Element::name(type), to get the name of the type of bar line. It's great that there's a version of all these names already free of embedded space characters.
There are probably other sub-types that could be specified in the SVG file as well if so desired. Articulations, for example?

As an aside, these are generally referred to in the code as element names, but they are the element-type names. Elements don't have names. Not complaining, just clarifying.

Attachment Size
eName.svg 78.36 KB

In reply to by Nicolas

Or the bar lines could do something like this: {syntaxhighlighter SPEC}data-type="BarLine_end"{/syntaxhighlighter}
One attribute = element-type separator sub-type
You could also leave out the sub-types in MuseScore. I find them useful for bar lines, but it might not be everyone's cup of tea.

Regarding the "class" attribute, it doesn't really have sub-classes, the whitespace separator the way you use it is more like the & operator in C++, it styles with both classes. The first answer in this post describes it nicely: http://stackoverflow.com/questions/9212955/am-i-subclassing-my-css-corr…

So that doesn't line up conceptually or practically. "class" would probably be best with a single value.
Regarding bar lines in this context: stroke-width is one of the CSS attributes, and the different types of bar lines are distinguished by their stroke-width.

In reply to by sideways

It's not about subclassing. By having class="barline end" (I would use lowercase here... just my preference), we would address the use case of coloring all barlines with the same color and another use case of coloring all end barlines with a given color.

In reply to by sideways

Re: Dual scaling factors and the 2_Scales.svg file I posted above (and here for your convenience):
With two exceptions, all of the elements in the file are being scaled by 0.9936. For most of the path elements it's via transform="matrix()". For the polyline elements and SlurSegments it's via stroke-width.
So for the paths, the number 0.9936 is staring you in the face, twice per element. For the polylines you must divide all of the stroke-width values by 0.9936 to see the connection. Fortunately there aren't that many different values because it mostly just varies by type. Here is a pseudo-table of the results in 3 columns:

SVG File /.9936 Element::Type
0.34776 0.3500 SlurSegment 4
0.39744 0.4000 StaffLines 5
0.79488 0.8000 System bar line 75
0.79488 0.8000 BarLineType::END 6
2.48400 2.5000 BarLineType::END 6
0.79488 0.8000 BarLineType::NORMAL 6
0.64584 0.6500 Stem 12
0.49680 0.5000 Tuplet 33 (polylines only, not paths)
0.74520 0.7500 LyricsLineSegment 49

The /.9936 column's values are clearly more sane than the values in the SVG file. Keep in mind that these values are using 72dpi, and are thus 25% lower than in the other files I've posted, which were all at 96dpi.

The two exceptions are both path elements:
1) Text of any kind. Tuplet text 33, tempo text 34, and lyrics 28 in this file.
2) Beams 26. They use fill-rule="evenodd", but other than that they seem normal.

So what's going on here? Why scale two different ways, the pen and the matrix? Why scale by such a tiny amount, 0.64%? Why scale at all, if you're going to scale everything? What about beams and text?
I'm posting the MuseScore file that is the source for these SVG files, in case that helps anyone decipher this. If you export from v2.0.2, you'll see 3.105 and 3.125 as the dual scaling factors. 2_Scales.svg was generated with changes to file.cpp that set scaling to 1:1. 0.9936 = 3.105/3.125 and 0.9936:1 = 3.105:3.125

Attachment Size
2_Scales.mscz 10.07 KB
2_Scales.svg 77.81 KB

In reply to by sideways

And here's the source of the 0.9936 scaling factor (using accidentals as the example in v2.0.2 code):

accidental.cpp, Accidental::draw() - the last line of code:{syntaxhighlighter SPEC}score()->scoreFont()->draw(e.sym, painter, magS(), QPointF(e.x, 0.0));{/syntaxhighlighter}
element.cpp, Element::magS(): {syntaxhighlighter SPEC}return mag() * (_score->spatium() / (MScore::DPI * SPATIUM20)){/syntaxhighlighter}
mscore.h:{syntaxhighlighter SPEC}static const qreal PPI = 72.0; // printer points per inch
static const qreal SPATIUM20 = 5.0 / PPI; // size of Spatium for 20pt font in inch{/syntaxhighlighter}
score.h:{syntaxhighlighter SPEC}qreal spatium() const { return style()->spatium(); }{/syntaxhighlighter}
style.h:{syntaxhighlighter SPEC}QSharedDataPointer d;{/syntaxhighlighter}
style.cpp, MStyle::spatium():{syntaxhighlighter SPEC}return d->spatium();{/syntaxhighlighter}
In Element::magS(), the values are:
mag() = 1
_score->spatium() = 6.6244444
MScore::DPI = 96
SPATIUM20 = 0.694
The result of the formula in magS() is 0.9936.

It looks like the issue is in style.cpp and whatever is inside QSharedDataPointer d->spatium(). What is StyleData? I can't find it anywhere in MuseScore except style.cpp and style.h, and it's not in the Qt help.

In reply to by sideways

I found the problem, and I believe it to be a bug. The change in scale occurs when you switch a score's Page Layout from mm to inches. I have attached 2 .mscz files and 2 corresponding .svg files (in 72dpi, I tested this against the master code). The only difference in the files is the inches vs. millimeters setting. I'm guessing there's a fixed conversion factor, instead of a library conversion function, and this is some kind of rounding error. But that's just a guess. Someone here knows a lot more about this than I do. Kind of cool that it's uncovered at the same time as a general scaling change was made.

I have seen another score: https://musescore.com/user/4215981/scores/1161361 where the scaling factor is approximately 0.8496. I don't know what version of MuseScore was used to create it, or what the difference is there, but it's something further to look into.

Attachment Size
mm72.mscz 6.38 KB
inches72.mscz 6.11 KB
mm72.svg 75.04 KB
inches72.svg 75.56 KB

In reply to by sideways

OK, one more post on these spatium irregularities:

1) As you might expect, the inch/mm scaling factor error multiplies each time you switch between inch/mm, getting worse each time.

2) There is a second "bug": If you start with a new, blank score, then in Page Layout change the height, width, or uncheck the "two-sided" box, the scaling changes by a different, tiny amount (approximately 0.0063%). This effect is not cumulative, it is a one-time alteration to the scaling factor (spatium) in this file. Further changes to the Page Layout settings do not cause further alterations to the spatium. I've attached an altered mm72.svg so you can see the numbers there. Note that I'm not spelling these numbers out to 6 digits of precision, MuseScore and Qt are. I set stroke-width to 2 digits of precision because I can avoid the first issue by never changing to inches (I'm a gringo, a yank, we use inches and "letter" sized 8.5x11inch paper).

3) Both these "bugs" affect not only scaling factors in transforms, but in the points= x/y coordinates for each polyline.

4) Yes, truncating to 2 decimal places probably solves the second, tiny, alteration, but it would be best to root all this out and solve it now, while scaling is fresh in everyone's minds.

Attachment Size
mm72.svg 77.51 KB

In reply to by sideways

I have pinned down the source of issue #2 in my previous post, in newwizard.cpp:
NewWizardPage4::NewWizardPage4(QWidget* parent)
...
fil.append(QFileInfo(QFile(":data/Empty_Score.mscz")));
...}

This code is called by file.cpp, MuseScore::newFile():
QString tp = newWizard->templatePath();
...
Score::FileError rv = Ms::readScore(tscore, tp, false);

I cannot find Empty_Score.mscz on my file system, and I don't understand what ":data" resolves to, but I do know that the contents of that .mscz file are the source of the problem. A new score, in the wizard, has a spatium of exactly 5. After leaving the wizard, MuseScore applies the styles from Empty_Score.mscz, and the score's spatium changes to 5.00031496, a 0.0063% difference, the same factor I see in my SVG files.
What is Empty_Score.mscz? How do I open it? And how do you change the spatium in a score file via the MuseScore UI? In other words, how do I fix this, or reset it?
Thanks.

In reply to by sideways

":data/Empty_Score.mscz" is the buitlin version of ".../templates/01-General/00-Blank.mscz". Interetsingly it has a Spatium setting of 1.76389, which then gets shown (rounded) as 1.764.
Would that explain it?
Not sure where you see a Spatium of 5 though, this would be pretty huge

In reply to by Jojo-Schmitz

During debugging, inside newFIle(), I check the value of this->-style->d->_spatium, and it equals 5, at least prior to applying the styles from Empty_Score.mscz. In other related procedures all the pointers to scores have that spatium value. I've seen it at 6.666667 too in other scores.

This rounding error is the problem. How do we fix it?

In reply to by sideways

The UI only shows 3 digits after the decimal dot/comma, so no change there.
And always rounding to what gets displayed should get rid of that mm vs. inch rounding issue at the same time, I think?
Another, possibly additional, fix might be to fix all the templates to use 1.764 rather than the odd value they have now. Won't do any good for existing score created from those though.

In reply to by Jojo-Schmitz

No, this problem is in the 5th/6th digits. 0.0063% = 0.000063. The rounding to 5 digits doesn't mess it up enough. I've been running these numbers in MS Excel to compare them all. I think the rounding needs to eliminated. Though I can see how that would be messy in the UI, but you can just left-justify the number in the UI field and let the extra decimal places scroll out of view. This is really an issue for the default value, not any user-modified value.

In reply to by Jojo-Schmitz

Ah, I forgot that the I in DPI stands for Inch. Yet again the USA has inflicted grief on the rest of the world :-)

Because of the I in DPI, this whole problem might be made simpler if MuseScore stored things in inches instead of millimeters. I realize it's a blasphemous idea, but I was born in the USA. It would probably cause all kinds of other cascading changes in the code, so it's probably not plausible, but it seems worth mentioning as a possibility. It would eliminate conversion factors in calculating DPI.

It begs the question: Which is more core, more central to MuseScore, millimeters on a printed page or DPI on the screen? In the end, the printed page gets reduced to DPI too, even if it's A4 metric paper, so DPI could be more of a core metric than mm, and inches could be the way to go.

You could store DPI instead of mm or inches. If DPI is fixed at 72, then it should be truly fixed and mm and inches should be calculated. It might reduce the quantity of conversions happening in the code, since DPI is at the center of it all. If someone wants to scale the score, they would scale it to a different DPI number, a whole number.

In reply to by sideways

Hmmm, no response to my last idea. It really is a good idea, unless of course I'm way off about how DPI/mm work in MuseScore. In the Page Setting dialog you could have a counter type up/down field for DPI, and alongside it could be scale factor (even %), and page size in inch/mm.

The user could thus be forced to pick a scaling factor that could be represented by a whole number DPI value. It really wouldn't be overly restrictive on the scaling factor. There would be no rounding or conversion errors, as there would only be conversion to mm and inches, which never actually get used for anything except converting values back to DPI.

Yes, users might be initially confused, but they'd get the hang of it.

In reply to by Jojo-Schmitz

Yea, you're probably right. I'm a sucker for simplification.

So is there a fix for this little scaling problem? I can hack it on my end with hard-coded numbers in C++, as I will be doing my scaling in the browser, via SVG. But I don't see an real fix I can make. Is the rounded number of mm part of the file format, a fixed width field? If so, there's no fixing this for the time being.

In reply to by sideways

Hmm, if you change the spatium, and are in mm, what gets saved is exactly what you see, 3 decimal digits. If you're in inch though it seems 4 decimals get stored (and converted to mm), that last digit then gets lost when switching back to mm.
Another problem is the 5 decimals in the templates.
If we get it fixed so that always no more then 3 decimals get stored, properly rounded, would that fix the problem?
Seems page height, width and borders always get sored with just 2 decimals, just as displayed

In reply to by Jojo-Schmitz

I think more decimal places is the answer, not less. Maybe 8? After around 12 floats get weird. I would suggest no rounding at all in the code though - why round a qreal? And then the Page Layout dialog can't round either.

As far as the file is concerned, is it storing the value as a 32 bit binary, or as numeric text? If it's binary, then why round at all? If it's not binary, why not?

Regarding storing DPI instead of mm, that's why I'm puzzled by your statement that the file format would have to change. If the file stores a real/float as 32 bits, what difference does it make what units those bits use once the code converts them to a number? 72 takes up the same 32 bit space as 1.76389 or 1.764.

Regarding page height and width, those numbers are used in addition and subtraction operations, not multiplication. It's the multipliers that cause the real damage.

In reply to by sideways

I see no sense in storing more digits than you can see and set in the UI.
The file is storing these as text. Just save a file as mscx and look at it with a plain text editor

Only thing that seems really important is that when flipping between mm and inch that stored value doesn't change at all, and 'converts' back and forth to the same result.

Whether you use inch or mm is not recorded in the file at all, it isn't even sored as a session setting in MuseScore. So it is just for convenience when showing/setting those values, but gets converted into mm in any case.

In reply to by Jojo-Schmitz

This might solve most of the inch to mm problem, but it doesn't get rid of the tiny rounding error caused by Empty_Score.mscz. You're right that if the UI doesn't display it there's no reason to store it. But there will continue to be small rounding errors if you store rounded values that are later multiplied. I would hope to get more precision in the UI, if DPI can't be adopted as the core scaling value.

The file isn't the issue though. It's an XML format, and the length of the field is variable. The file could store any numeric value, regardless of units or precision.

In reply to by sideways

the file could store anything, but the program needs to understand that and needs to distinguish whether it is mm, inch, or DPI. And there you have a compatibility issue.
The Empty_Score.mscz problem could get solved by changing it to have 1.764, which matches what gets shown. Same for all other templates.

In reply to by Jojo-Schmitz

No, that is not correct. The problem is that 1.76389 is a calculated value based on all the fixed constants:

1.76389 = 5 / (72dpi / 25.4mm-per-inch)

Now I don't know where that 5 value came from. It's the default initial MStyle _spatium value.
Regardless, when you save 1.76389 as 1.764, the resulting _spatium value is not 5 anymore, it's 5.000314961, thrown off by that rounding factor.

In reply to by Jojo-Schmitz

Actually, I looked that up and it appears to be exactly 25.4. Even the MS Excel CONVERT() function does not have any additional decimals, and neither do any sources on the web. Amazing, isn't it?

So I'm going to round that _spatium value in my code, what's the next step in MuseScore?

In reply to by Jojo-Schmitz

Maybe it's the reverse conversion from inch to mm that is flawed? MS Excel Convert() has the reverse conversion factor at: 0.039370079.

Regardless, you have me convinced that if everything is rounded to the same precision, including all the results of multiplication operations, there is no need for "extreme" precision.

yes - MStyle::setSpatium() and MScore::setSpatium() seem to call each other in a semi-endless loop when trying to debug the source of that number 5. I don't see where the variable gets set either. Maybe it's in one of those _p.h files?

Though the setting of that value occurs when the initial Score object is created in newFile(), prior to opening the wizard.

In reply to by Jojo-Schmitz

Since 1959, one inch has been defined as 25.4 mm exactly, or if you prefer one foot = 0.3048 metre exactly. See the first paragraph of https://en.wikipedia.org/wiki/Foot_%28unit%29 . Often in practice people multiply by 0.03937 to convert from millimetres to inches rather than dividing by 25.4, or multiply by 3.2808 to convert from metres to feet rather than dividing by 0.3048, thus introducing a rounding error. 0.03937 is not equal to 1/25.4, and 3.2808 is not equal to 1/0.3048.

In reply to by sideways

I now see where the spatium value of 5 is being set:
mscore/pagesettings.cpp/PageSettings::updateValues()
if (mm) {
suffix = "mm";
singleStepSize = 1.0;
singleStepScale = 0.2;
}
else {
suffix = "in";
singleStepSize = 0.05;
singleStepScale = 0.005;
}
...
spatiumEntry->setSuffix(suffix);
spatiumEntry->setSingleStep(singleStepScale);

0.2 = 1/5, so in the default units of "mm", the spatium is set to 1:5. Invert that and you get 5:1.

This might also be the source of the additional rounding errors with inches. As discussed previously in this thread, the conversion rates between mm and inches are: 25.4 and ~0.0394. The relationship established between mm and inches in this code creates these conversion rates: 40 and 0.025. This actually seems way worse than the rounding errors I saw in inches, but regardless, it's an odd bit of code. Is there an explanation for this, or is this part of the rounding issue for inches?

Holy crap! Now I get it: If .2 = 5, then .025 = 25 and 40 = . 04, and then it makes perfect sense as the source of the rounding error: 25.4 rounded to 25 and 0.0394 rounded to 0.04. Or something like that, I realize my math is still a bit iffy here.

In reply to by shoogle

Exactly. I had already edited the post twice, so I let it slide. The point is the same, but your math is correct, here it is relative to the rounding errors:
0.2 is to 5 as
0.025 is to 40 as
.04 iis to 25.

So the rounding errors when converting to inches are as I stated: 25.4 gets rounded to 25, and 0.039 gets rounded to 0.04.

This code should use proper conversion factors, not these hard coded, rounded values. There are constants defined for this, some of them recently updated for the 72dpi changes.

Do you still have an unanswered question? Please log in first to post your question.