Closed 0xfe closed 2 years ago
Almost all of this information is included in the JSON files that accompany glyphs in the SMuFL standard. If something like this takes place, I'd definitely recommend using the SMuFL glyph names ("rest16th" etc.) as a way of standardizing them.
When I looked at the font file, it didn't seem to have half the glyphs used in VexFlow. Do you know if I'm looking at the wrong thing?
So I've began working on this, you can see my branch here: https://github.com/Silverwolf90/vexflow/tree/refactor/extract-hard-corded-glyphs
So far I've removed every hard-coded gonville font code in favor of SMuFL glyph names, and moved those codes to a file called smuflgonville.js
, this file contains the SMuFL names and then under each name is the gonville code and meta data (although I haven't finished extracting all metadata about the gonville glyphs yet).
To make everything work I added a little hack at the beginning of Glyph.loadMetrics
var glyph;
if (code[0] === "v"){
glyph = font.glyphs[code];
} else {
var gonvilleData = Vex.Flow.SMuFLGonvilleMap[code];
glyph = font.glyphs[gonvilleData.code];
}
This little "code interception" is necessary because sometimes we create a new Glyph("<smuflName">)
directly and other times there is a layer of abstraction (like in articulations/ornaments/accidentals) which houses the meta-data for the glyph as well as the gonville code, which the code reads and calls new Glyph("v<gonvilleCode>")
In tables.js
you can see the accidental/ornament/articulation codes and have been refactored to pattern:
Vex.Flow.articulationCodes = function(artic) {
var smuflName = Vex.Flow.articulationCodes.articulations[artic];
return Vex.Flow.SMuFLGonvilleMap[smuflName];
};
Vex.Flow.articulationCodes.articulations = {
"a.": "articStaccatoAbove", // Staccato
"av": "articStaccatissimoAbove", // Staccatissimo
"a>": "articAccentAbove", // Accent
"a-": "articTenutoAbove", // Tenuto
"a^": "articMarcatoAbove", // Marcato
"a+": "pluckedLeftHandPizzicato", // Left hand pizzicato
"ao": "pluckedSnapPizzicatoAbove", // Snap pizzicato
"ah": "stringsHarmonic", // Natural harmonic or open note
"a@a": "fermataAbove" , // Fermata above staff
"a@u": "fermataBelow", // Fermata below staff
"a|": "stringsUpBow", // Bow up - up stroke
"am": "stringsDownBow", // Bow down - down stroke
"a,": "breathMarkComma" // Choked
};
And the data has been moved to smuflgonville.js
.....
"articStaccatoAbove": {
code: "v23",
width: 4,
shift_right: -2,
shift_up: 8,
shift_down: 0,
between_lines: true
},
"articStaccatissimoAbove": {
code: "v28",
width: 4,
shift_right: 0,
shift_up: 11,
shift_down: 5,
between_lines: true
},
"articAccentAbove": {
code: "v42",
width: 10,
shift_right: 5,
shift_up: 8,
shift_down: 1,
between_lines: true
},
"articTenutoAbove": {
code: "v25",
width: 9,
shift_right: -4,
shift_up: 17,
shift_down: 10,
between_lines: true
}
....
(although I'm realizing now that between_lines
is not glyph specific information, but rather articulation specific information).
I'm at this point where I don't really know where to go next. So some direction/thoughts/comments would be good.
Actually, what I need to do next is a real pass on extracting all glyph related. However, I think the part that will need thought is the whole "plug-in" architecture for different fonts. Are we expecting the raw glyph data to be encoded in the same way as Vex.Flow.Font
? Do we have a separate .load<Font>Metrics()
for every font?
Additionally, there will probably have to be a bit of Stem refactoring to happen. Currently every note duration type has a stem_extension
, stem_extension_gracenote
and stem_extension_tabnote
, mainly used for flag positioning. This is really just data that should be extracted into the glyph data. However, in the case of whole notes/breve notes the stem extension is -Stem.HEIGHT
which is pretty ugly. And for quarter notes, the extension is 0
. The extensions for gracenotes are all negative. When really we need a constant like Stem.HEIGHT_GRACENOTE
, and then a positive stem_extension_gracenote
for each flag.
Also, the moving of all this data really means that the following names are no longer clear. As glyphs and font "codes" are now abstracted further. Glyph data and the notation element data are separated.
Vex.Flow.durationToGlyph
durationToGlyph.duration_codes
StaveNote.getGlyph()
accidentalCodes
ornamentCodes
articulationCodes
Actually maybe articulationCodes
, accidentalCodes
, ornamentCodes
are ok because they're referencing the string code used on construction for a specific element.
Great start Cyril. This is not an easy endeavor, so you need to do it in bite-sized pieces (which means that you might have to change things around a bit as you make progress.)
If you've finished the glyph name indirections then this would be a good point to do the first merge. People can start using it early and making sure that nothing breaks.
Re: Vex.Flow.Font
, I don't particularly care if you use the same encoding, but I do prefer to have a consistent encoding across all fonts. This would simplify making changes, refactoring, and overall maintenance.
Also, I would move all font related data (like tables and encodings) into font/
so as to keep the actual code uncluttered.
Re: next steps. Get the new font to render and swap in. There are lots of little bits of hardcoded metrics that will need to be extracted from the code, and if you have a working renderer, it's much simpler to eyeball the tests to see how well your changes work.
Also, this is a good time to design the indirection structures. I propose the following:
Vex.Flow.FontLoader
class that is responsible for holding the current font and indirection tables.
Vex.Flow.Glyph
might use it to resolve glyph codes from SMuFL codes.setFont
(or something.)font/
you can have <fontname>_maps.js
, <fontname>_metrics.js
, and <fontname>_font.js
, containing the codes, metrics (see below), and actual font curves respectively. Maybe even a <fontname>_main.js
that does some simple initialization for the font, such as registering it with FontLoader
.By metrics, I mean all the little hardcoded shifts, sizes, widths, etc. that currently exist in the code. So you might have a "stem.flag.top.y_shift"
(arbitrarily using dots for hierarchy) that is set to 15
in goneville_metric.js
and 10
in smufl_metrics.js
. The metric names and hierarchy will evolve as we develop this.
One more thing that is very important is a simple visual tool (think src/transform.html
) that allows you to swap fonts on the fly and eyeball the differences in a grid. This would vastly aid development and debugging. You might want to have this ready before you actually start working on the metrics.
Anyhow, we probably won't get the design right the first time, so lets work on this incrementally, and see how things go.
Good ideas. Let's not call the font for metrics "smufl" as in "smufl_metrics" but if we're using Bravura (the only major smufl font so far) call it "bravura_metrics".
The way to support smufl, in my mind, would be to have a separate FontLoader subclass called SmuflFontLoader that can get all the information about maps and metrics from the standard smufl fontname_metadata.js
Yes, sorry, I meant bravura_metrics, I was conflating the two.
On Sat, Aug 16, 2014 at 9:30 AM, Michael Scott Cuthbert < notifications@github.com> wrote:
Good ideas. Let's not call the font for metrics "smufl" as in "smufl_metrics" but if we're using Bravura (the only major smufl font so far) call it "bravura_metrics".
The way to support smufl, in my mind, would be to have a separate FontLoader subclass called SmuflFontLoader that can get all the information about maps and metrics from the standard smufl fontname_metadata.js
— Reply to this email directly or view it on GitHub https://github.com/0xfe/vexflow/issues/181#issuecomment-52393080.
Mohit Muthanna [mohit (at) muthanna (uhuh) com]
So far I've extracted every hard coded font code and replaced them all with the appropriate SMuFL glyph name. Tests are all rendering fine.
So to be clear, Gonville needs the following font files:
gonville_font.js
vexflow_font.js
to this, this file contains all the Gonville font codes (ie: v1
, v4b
, v51
)gonville_map.js
gonville_font.js
,gonville_metrics.js
width
, x_shift
, stem_down_extension
)For bravura, the associated "code" should just be the unicode code point, and this mapping already exists in smufl_metadata.js
. See the following example:
"accidentalSharp": {
"alternateCodepoint": "266F",
"codepoint": "U+E262",
"description": "Sharp"
},
From the .otf
file I'll use opentype.js to generate the bravura_font.js
file which will only contain the glyph paths that VexFlow needs, the keys in this table are the unicode code points. However, @mscuthbert, don't we need a bravura_metrics.js
because we'll still need to put fine tuning data somewhere?
Well reading through the spec more... It seems that SMuFL is incredibly thorough! As glyphs that have "attachments" have coordinates to express where the attachment should happen.
But this poses a problem for certain calculations in VexFlow, like stem extending for flags, because the data is drastically different.
In Gonville, currently our metrics data for a 128th flag looks like this:
{
...
"flag128thUp": {
"stem_up_extension": 26,
"gracenote_stem_up_extension": 6,
"tabnote_stem_up_extension": 22
},
...
}
In Bravura, the metrics data for a 128th flag looks like this:
"glyphBBoxes": {
...
"flag128thUp": {
"bBoxNE": [
1.044,
2.452
],
"bBoxSW": [
0.0,
-2.928
]
},
...
},
...
"glyphsWithAnchors": {
...
"flag128thUp": {
"stemUpNW": [
0.0,
2.296
]
},
...
}
It might be better, at least as an intermediate step, to simply create a bravura_metrics.js which has the more simplified metrics (which simply relies on trial and error top determine adequate values) like Gonville's. And at a later time, we aim to effectively use the bravura_metadata.js
Yes we definitely need bravura_metrics.js
(for now.)
The attachment information is excellent -- I actually started building a whole attachment based container system for exactly this. It's still in my working directory. I think its a good long term goal to render directly off the metadata, which may also mean creating a metadata file for Gonville.
I think that it'd be better to have a translator from the SMuFL to the Gonville/current VexFlow -- SMuFL really looks like it's going to be the standard for the future, so it'd let Vexflow talk with more systems. Just been working a lot on the music theory teaching -- VF has been amazing; I'm using Bravura as well and the two do play with each other pretty well.
I agree, that would be better. So this will definitely be something we have to iterate upon.
I have Bravura rendering, although the FontLoader
I created is very primitive. I didn't actually end up creating a map for Bravura. I think having a level of indirection with the unicode codepoints is a bit unnecessary if SMuFL is really going to be the standard. It'll only be necessary to have a map for non-standard glyph names, like we do for gonville. Any tool we create to convert a font to a usable format by vexflow should have access to the SMuFL glyphnames to codepoint mapping (that is available in the SMuFL json metadata). Currently I used opentype.js
to parse the bravura.otf
file, took the glyph data and dumped a json structure with all of it (which you can see in bravura_font.js
, the file which executes this conversion/dump is fonts/test.html
). Then I took opentype's Path
class and glyph drawing code and used that to render from these json structures (all this code is simply tacked on to the bottom of Glyph.js
at the moment)
My current branch is here: https://github.com/Silverwolf90/vexflow/commits/opentype-fun
The commit that has the majority of the changes in the codebase is called "butcher codebase to get Bravura rendering". Which, as the commit message should indicate, I'm not particularly happy with what I did. However, both gonville and Bravura render. So I guess it's something ¯(°_o)/¯
At the top of tests/flow.html, you'll see the following line
Vex.Flow.FontLoader.setFont(Vex.Flow.Font.Bravura);
Which could also be:
Vex.Flow.FontLoader.setFont(Vex.Flow.Font.Gonville);
I still have not extracted all hard coded font sizes. A lot of clean up still has to be done.
More thoughts!
As I started extracting font sizes, I realized that in some cases we need to keep track of multiple sizes for some glyphs. So far I've come across this with stavenotes/gracenotes and accidentals/cautionary.
So I'm moving the hard-coded font size values to a proportional scale. For example, gracenotes would have scale of 0.6 (ie: 60%)
Also, it seems to me that the hard-coded glyph widths (noteheads, articulations, ornaments, accidentals) should be removed in favor of calculating glyph widths from the raw data itself.
In fact, the eye-balled x_shift values should also be removed. Particularly if we have the raw glyph width, we should use that to normalize origins to the left because the SMuFL standard is to have origins to the left. For example, ornament glyphs in Gonville have a centered horizontal origin position. So if we divide the the width of an ornament by two, and use that value as the x shift, it will make for a more accurate position without eye-balling. And the only data we have to keep track of are origin positions that are not to the left (meaning that left origins are the default).
+1 for proportional scale +1 for calculating widths from raw data (although in some cases, there is padding included)
I'd love to see some screenshots of VexFlow using Bravura. :-)
Will be getting back into this soon. Here are some screenshots of what it looks like so far:
Using the bravura_metadata.json
file we can also get accurate bounding boxes on a per glyph basis. Something the Gonville data (in its current form) does not provide. And would be very useful for automatically vertically aligning glyphs.
What would your thoughts be on making Bravura the default VexFlow font? It has a far more comprehensive set of glyphs.
That would be an amazing and positive result if it could be done without substantially increasing the download time on using Vexflow. FWIW, I already use Bravura extensively in my sites in order to display music notation outside of Vexflow, so it'd actually reduce my download times.
On Wed, Sep 17, 2014 at 10:54 AM, Cyril Silverman notifications@github.com wrote:
Using the bravura_metadata.json file we can also get accurate bounding boxes on a per glyph basis. Something the Gonville data (in its current form) does not provide. And would be very useful for automatically vertically aligning glyphs.
Very good. I also noticed that it has all kinds of control point data, which vastly simplifies alignment and tuning.
[image: image] https://cloud.githubusercontent.com/assets/1631625/4305488/bc570882-3e79-11e4-959d-39ad3f922edc.png
What would your thoughts be on making Bravura the default VexFlow font? It has a far more comprehensive set of glyphs.
I fully support it, so long as we can make the upgrade process painless. E.g., if I update my.vexflow.com to use the new version, will it significantly worsen the renderings (which are all VexTab)?
One way to test this is to eyeball the VexTab tests with both the old and new fonts.
— Reply to this email directly or view it on GitHub https://github.com/0xfe/vexflow/issues/181#issuecomment-55905783.
Mohit Muthanna [mohit (at) muthanna (uhuh) com]
That will be a good test (and actually, Daniel Spreadbury, the Bravura inventor, would probably be very interested). TAB is an extensive and well developed feature of Vexflow/Vextab, while it's somewhat of an afterthought on a lot of notation systems (my own work included, sad to say) so if there are going to be any errors, they're going to arise there...
Download time would be effectively the same, since VexFlow only uses a small subset of glyphs, we'd need a tool that lets you select which glyphs to include in the font object (like transform.html
), I have a very rough version of that already. After optimizing the glyph paths into the glyph outline string the bravura_font.js
file is 115kb.
My goal is that you should just be able to plop vexflow-min.js (built with the bravura font file) and everything should basically render exactly the same. So I'll definitely have to take a look at the vextab tests to see how well that works.
The last major piece of work I have to do on this refactor is to remove the references to head_width
(the hard coded notehead width) throughout the codebase.
This is how it feels fine tuning anything to do with stems :)
They really need a serious rework. Ideally something that would take into account the attachment points that bravura provides.
On another note, this branch has my latest work. You should be able to pull it and just open up the tests to get an idea. I'd say I'm like 95% there to getting Bravura to render as well or better than Gonville used to. If you plug in Gonville (by changing what the FontLoader
uses in flow.html
). It renders worse than bravura and worse than it did before, but is still pretty close as well, but I haven't done as much work to tune positioning.
Ha ha. Keep fighting the good fight.
Its probably worth thinking about statistical methods for tuning (i.e., to find a sweet-spot), similar to what we have in beams. I don't know if it actually applies here, but in some cases it might be better than using hard coded points.
On Fri, Sep 19, 2014 at 10:57 AM, Cyril Silverman notifications@github.com wrote:
This is how it feels fine tuning anything to do with stems http://i.imgur.com/pPeBAIJ.gif :)
They really need a serious rework. Ideally something that would take into account the attachment points that bravura provides.
— Reply to this email directly or view it on GitHub https://github.com/0xfe/vexflow/issues/181#issuecomment-56188500.
Mohit Muthanna [mohit (at) muthanna (uhuh) com]
The most serious current need with respect to stems, to me, is taking into account the position with respect to the baseline and ensuring that notes below (Treble) B below staff, or above (Treble) B above staff by default (unless there are multiple voices or the stem direction has been overridden) get stem extensions that reach to the midline. I adjust this in my output because it's really important to me, but I'm doing it based on the music21j representation of the note and not on the Vex representation of StaveNote, as I probably should. I haven't gotten it to interact well with auto beam though, I'm afraid.
I'll try playing with the awesome work Cyril is doing at some point soon, for now I'm thanking my stars that Vexflow is pretty stable because I'm throwing so many unstable wrappers around it, that I'm so glad that I can be 99% sure the problems that arise are mine and not Vex's.
btw -- do we need to convert the fonts to javascript/canvas/svg paths, or in Bravura, can they be added as text to the staff? It'd let us use gzip'd representations and perhaps make the entire download of Bravura.eot be smaller than a subset of Bravura in .js?
Yes, keep them as javascript paths. Otherwise, we are at the mercy of the system font renderer, which is 1) a nightmare to debug, and 2) has cross-platform issues with scaling, anti-aliasing, etc. (making it difficult to get a consistent appearance.)
On Fri, Sep 19, 2014 at 1:35 PM, Michael Scott Cuthbert < notifications@github.com> wrote:
btw -- do we need to convert the fonts to javascript/canvas/svg paths, or in Bravura, can they be added as text to the staff? It'd let us use gzip'd representations and perhaps make the entire download of Bravura.eot be smaller than a subset of Bravura in .js?
— Reply to this email directly or view it on GitHub https://github.com/0xfe/vexflow/issues/181#issuecomment-56209034.
Mohit Muthanna [mohit (at) muthanna (uhuh) com]
So Bravura has their line segments horizontally. Which means for strokes, rotation has to occur. Naturally, Canvas makes this fairly easy through .translate
and .rotate
methods. However, I really know nothing about Raphael. So I'm not how to modify the context wrapper to add similar functionality.
Can you explain what you mean by "line segments horizontally"? You can rotate individual elements in Raphael with "element.rotate()", if that helps.
If you look at raphaelcontext.js, there is an implementation for each Canvas call. If you need to add a new function to it, make sure you also add it to canvascontext.js and update the list of methods in renderer.js:bolsterCanvasContext().
On Mon, Sep 22, 2014 at 5:57 AM, Cyril Silverman notifications@github.com wrote:
So Bravura has their line segments horizontally. Which means for strokes, rotation has to occur. Naturally, Canvas makes this fairly easy through .translate and .rotate methods. However, I really know nothing about Raphael. So I'm not how to modify the context wrapper to add similar functionality.
[image: image] https://cloud.githubusercontent.com/assets/1631625/4354519/c965e7b4-423e-11e4-91cb-a5a5752adf6f.png
— Reply to this email directly or view it on GitHub https://github.com/0xfe/vexflow/issues/181#issuecomment-56351927.
Mohit Muthanna [mohit (at) muthanna (uhuh) com]
With bounding box data, we actually end up having really powerful control over glyph positioning.
I've implemented an interface where you can actually set the origin point:
glyph.setHorizontalOrigin('center');
glyph.setVerticalOrigin('bottom');
This makes glyph positioning far simpler. As you no longer have to calculate offsets, the Glyph
class handles it all for you based on width/height of the bounding box.
Examples....
_Accidental origins set to *_V: top H: center***
V: center H: right
@0xfe what I meant by "line segments horizontally" are that the actual glyphs which make up arpeggiation strokes are horizontal in Bravura, while in gonville they are vertical.
Glyphs: http://www.smufl.org/version/latest/range/multiSegmentLines/
Wow, it seems like it should be possible with this to set the entire complex of accidentals + ornament to avoid staff lines, beams, etc., quite easily!
VexTab with my Bravura changes so far:
@0xfe I did notice that you have a bug in VexTab right now. If you add an error in Glyph.render
that throws if X/Y is undefined or NaN you'll notice that some articulations are failing. Not exactly sure what's going on under the hood as I have not used VexTab.
Hi all
I realise this thread is quite old now (almost a year to the date!) but I was wondering whether Bravura or general SMuFL has been incorporated within Vexflow main branch? I have a fork of the source at the moment but would like to incorporate SMuFL as an option for rendering glyphs. Could you let me know@
Any information would be greatly appreciated!
Cheers Josh
What's about this?
i want to do a music sheet editor and i want to do a tool bar with the common music symbols, i don't know how implements the VexFlow glyph fonts for do it. :/
The SMuFL font used here is Bravura, right?
@SalahAdDin yes this is Bravura
SMuFL is not in the main branch of VexFlow. And the work done so far is definitely not ready to move into the main branch. It goes without saying that trying to support SMuFL is particularly intrusive, as changes are required throughout the entire codebase. And it's perhaps one that it's not ready for (went through a lot of pain to get it to the point that it's at).
As far as how fonts are stored, you can see the data here: https://github.com/0xfe/vexflow/blob/master/src/fonts/vexflow_font.js
It just stores a serialized version of the glyph outlines (with some other data as well). If you want to add new glyphs, you could probably use something like opentype.js to parse a font file and serialize the outlines of the glyphs you want into an identical structure.
I didn't know that this is so hard.
Hi Cyril
Is this something which you're willing to allow others to contribute to or are you wanting to continue work on it before making it more public?
Thanks Josh
On Mon, Apr 4, 2016 at 11:52 PM, Yusuf (Josè) Luis <notifications@github.com
wrote:
I didn't know that this is so hard.
— You are receiving this because you commented. Reply to this email directly or view it on GitHub https://github.com/0xfe/vexflow/issues/181#issuecomment-205530273
My work is already available here:
https://github.com/Silverwolf90/vexflow/tree/remove-hardcoded-head-width
But it's quite old.
@Silverwolf90 Make a pull request please.
I'm not sure I understand. What is the advantage of opening up a PR? Couldn't you just fork my repo?
I love reading historical discussions like this to learn about design decisions. :-) Someday maybe we can create a "best-of" list of PR discussions and stick them in our wiki somewhere, hehe.
But I think this issue is now solved since we now have good SMuFL support in VexFlow. (We also have support for web fonts, too!)
A good start would be the lilypond font or SMUFL.
This means:
1) Factoring out direct references to Gonville font indexes (e.g., "v51", "v7b", etc.) and putting them in a separate table (e.g., "closed_head": "v51", "16th_rest": "v8a", etc.).
2) The table must include all parameters related to the glyphs, e.g., scale, grace_note_scale, x_shift, y_shift, etc, bounding_box, etc.
3) The Glyph renderer should be able to render different JSON font formats.