0xfe / vexflow

A JavaScript library for rendering music notation and guitar tablature.
http://www.vexflow.com
Other
3.89k stars 661 forks source link

VexFlow should be able to "plug-in" alternate fonts. #181

Closed 0xfe closed 2 years ago

0xfe commented 10 years ago

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.

mscuthbert commented 10 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.

0xfe commented 10 years ago

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?

Silverwolf90 commented 10 years ago

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.

Silverwolf90 commented 10 years ago

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?

Silverwolf90 commented 10 years ago

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.

Silverwolf90 commented 10 years ago

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.

Silverwolf90 commented 10 years ago

Actually maybe articulationCodes, accidentalCodes, ornamentCodes are ok because they're referencing the string code used on construction for a specific element.

0xfe commented 10 years ago

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:

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.

mscuthbert commented 10 years ago

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

0xfe commented 10 years ago

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]

Silverwolf90 commented 10 years ago

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:

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?

Silverwolf90 commented 10 years ago

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.

image

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
    ]
  },
  ... 
}
Silverwolf90 commented 10 years ago

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

0xfe commented 10 years ago

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.

mscuthbert commented 10 years ago

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.

Silverwolf90 commented 10 years ago

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.

Silverwolf90 commented 10 years ago

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%)

Silverwolf90 commented 10 years ago

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.

Silverwolf90 commented 10 years ago

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).

0xfe commented 10 years ago

+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. :-)

Silverwolf90 commented 10 years ago

Will be getting back into this soon. Here are some screenshots of what it looks like so far:

image

image

image

image

image

image

image

image

Silverwolf90 commented 10 years ago

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.

image

What would your thoughts be on making Bravura the default VexFlow font? It has a far more comprehensive set of glyphs.

mscuthbert commented 10 years ago

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.

0xfe commented 10 years ago

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]

mscuthbert commented 10 years ago

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...

Silverwolf90 commented 10 years ago

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.

Silverwolf90 commented 10 years ago

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.

Silverwolf90 commented 10 years ago

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.

0xfe commented 10 years ago

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]

mscuthbert commented 10 years ago

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.

mscuthbert commented 10 years ago

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?

0xfe commented 10 years ago

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]

Silverwolf90 commented 10 years ago

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

0xfe commented 10 years ago

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]

Silverwolf90 commented 10 years ago

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*** image

V: center H: right image

Silverwolf90 commented 10 years ago

@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/

mscuthbert commented 10 years ago

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!

Silverwolf90 commented 10 years ago

VexTab with my Bravura changes so far:

image

@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.

jmahmud commented 9 years ago

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

SalahAdDin commented 8 years ago

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. :/

SalahAdDin commented 8 years ago

The SMuFL font used here is Bravura, right?

Silverwolf90 commented 8 years ago

@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.

SalahAdDin commented 8 years ago

I didn't know that this is so hard.

jmahmud commented 8 years ago

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

Silverwolf90 commented 8 years ago

My work is already available here:

https://github.com/Silverwolf90/vexflow/tree/remove-hardcoded-head-width

But it's quite old.

SalahAdDin commented 8 years ago

@Silverwolf90 Make a pull request please.

Silverwolf90 commented 8 years ago

I'm not sure I understand. What is the advantage of opening up a PR? Couldn't you just fork my repo?

ronyeh commented 2 years ago

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!)