Friday, 9 January 2015

Musical Symbols in WPF/Silverlight

I had some interest over this week looking at being able to show some musical symbols in a Silverlight / WPF application and had expected this to be a relatively simple thing but ended up chasing down quite a lot of little dark alleys until I found a couple of good answers. So, with my New Year's resolution of posting a lot more of these little thoughts, I'll see how far I can get describing the journey.

For starters I thought this would be simple. Everything is now unicode, right, they have Japanese, Korean, Maths notation and other symbols, there must be a code-page for musical notation. Yes! and well, no! If you go to the Unicode specification, there's a whole set of musical symbols defined. http://www.unicode.org/charts/PDF/U1D100.pdf Next bit of searching was to find a suitable font that had these symbols included. I finally found 'Symbola.ttf' which included it, but probably not my final font. Enough to try though. I dutifully installed this and then wanted to have a look at what symbols were there. The big problem is that these are in UTF-32. Take a look at charmap (on Windows), this only extends up to UTF-16. So I couldn't see them. Undeterred and still expecting this not to be a problem I had a long fight with Silverlight to try to get it to show this unicode encoding, finally giving up when I realised it only extended to UTF-16 (basically this means you can only go as far as \UFFFF or  as otherwise written. This is a problem, as the musical notation is all above that, with the treble-clef at U+1d120. Hmmpf.

So, where could I go then? I did have a poke around in some other fonts, and hoped that they would have musical notation in the private areas. More of which later, but at this point I didn't find quite what I was looking for and so, I'll continue my journey as it uncovered some other interesting things on the way to what I got to solve my problem.

Next step, I took a look at EasyABC, as I had posted previously I've been using this on my Mac and am really liking it, so I took a look at the package and the python scripts. First thing to note that the actual musical rendering is made by abc2ps, an excellent little project written in C. The musical format in this case was not quite what I was looking for (need to come back and detail a bit more when I get a chance).

After that, as I've been playing with it as well, I took a look at VexFlow. Now, this was interesting, digging down in the source on github, they have the file gonville.js  that appears to have some interesting path-like font descriptions. In trying to understand this I uncovered quite a long and interesting thread on pluggable fonts for VexFlow that I must go back and read in more detail.

The thread lead me on to SMuFL - Standard Music Font Layout (http://www.smufl.org/) - now I was getting somewhere! I had an interesting read around this and found the Bravura.otf OpenText format font. This revealed a number of interesting things - firstly it is available under the SIL Open Font license and secondly it implements all of the specified unicode musical symbol range U+1d100 - U+1d1dd, however these are mapped into the unicode Basic Multilingual Plane in the standard private area at U+e000 so fall within the scope of UTF-16 and hopefully usable by Silverlight! Now we're sucking diesel!

Next step I returned to the previous bashing of my head against a Silverlight wall and with a bit more googling, finally got the font in and showing a treble-clef. There's a quite a few false trails out there and the Silverlight route throws up some slightly different challenges to a standard WPF app, but the best advice was from Apres Pro Silverlight 5 in C# here on Font Embedding  It's important to follow the note on using the # to show the name of the font, not just the file containing the font.

My code directly in WPF finally looked like this...

<TextBlock Name="trebleclef" FontSize="52" FontFamily="/AgScore;component/Fonts/Bravura.otf#Bravura">&#xf472;</TextBlock>

In this case I have the Bravura.otf font file in a sub-directory called Fonts and the properties have Build Action set to Resource and Copy to Output set to Copy if newer. The name of my application is 'AgScore'. Nice!

Now, there's an interesting little note in the Apres book that says if you can't get this working for you in your app, then turn the fonts into graphics and use the Path notation. Nice! To backtrack a little. I had tried this unsuccessfully earlier when I took one of the lines of the gonville.js file path-like notation and had converted that into the path mini-language. It sort of worked, but I didn't have the energy to follow that through when I thought there should be a simpler answer and fond the SMuFL links. The little note in the book points to a link hich details how to get the text into a path string from code. I didn't want to fight this from Silverlight itself and the approach shown in the link uses a backend service by the looks of it, so I decided to bash at this directly in a C# application and get the path information to see how it worked. Here's how it looks:


    // Set up the Culture
    string strCulture = "en-us";

    System.Globalization.CultureInfo ci = new System.Globalization.CultureInfo(strCulture);

    // Set up the flow direction
    System.Windows.FlowDirection fd = FlowDirection.LeftToRight;

    // Set up the font family from the parameter
    FontFamily ff = new FontFamily("D:\\Users\\gbcollia\\Documents\\Visual Studio 2010\\Projects\\AgScore\\AgScore\\Fonts\\Bravura.otf#Bravura");

    // Create the new typeface
    System.Windows.Media.Typeface tf = new System.Windows.Media.Typeface(ff,  
                                                                        FontStyles.Normal, 
                                                                        FontWeights.Normal 
                                                                        FontStretches.Normal);

    // Create a formatted text object from the text,

    // culture, flowdirection, typeface, size and black

    FormattedText t = new FormattedText("\uf472", ci, fd, tf, 20, System.Windows.Media.Brushes.Black);

    // Build a Geometry out of this
    Geometry g = t.BuildGeometry(new Point(0, 0));

    // Get the Path info from the geometry
    PathGeometry p = g.GetFlattenedPathGeometry();

    // Get the path data info
    string pathdata = p.ToString();

  
I then unashamedly  pasted this path data into a path in SIlverlight to see how that looked as follows:

<Path  Stroke="Black" Fill="Black" StrokeThickness="0.1" Height="80" Width="30" Margin="0" Data="F1M8.81999969482422,38.7800025939941L9.28749942779541,41.7350006103516 9.67999935150146,44.3600006103516 11.2150001525879,43.2925033569336 11.6999998092651,41.6400032043457 11.6999998092651,41.4200019836426 11.4496870040894,40.2987518310547 10.852499961853,39.4700012207031 8.81999969482422,38.7800025939941z M6.83999967575073,33.4400024414063L4.96499967575073,34.8790626525879 3.37499976158142,36.4025001525879 2.27250003814697,38.1846885681152 1.85999989509583,40.4000015258789 1.85999989509583,40.560001373291 2.47249984741211,42.6812515258789 3.86499977111816,43.9550018310547 5.6399998664856,44.5762519836426 7.39999961853027,44.7400016784668 8.69999980926514,44.5800018310547 7.67999982833862,38.8800010681152 6.2024998664856,39.4775009155273 5.70843744277954,40.080940246582 5.51999998092651,40.9600028991699 5.51999998092651,41.1599998474121 5.94499969482422,42.2800025939941 6.6399998664856,42.9200019836426 6.94000005722046,43.2800025939941 6.69999980926514,43.4400024414063 6.27999973297119,43.3600006103516 4.96999979019165,42.4300003051758 4.1399998664856,40.7200012207031 4.01999998092651,39.8400001525879 4.26656246185303,38.5371894836426 4.94250011444092,37.3675003051758 5.95218753814697,36.4340629577637 7.19999980926514,35.8400001525879 6.83999967575073,33.4400024414063z M9.17999935150146,22.7200012207031L8.14781188964844,23.1481266021729 7.41749954223633,24.1650009155273 6.83999967575073,27.0800018310547 6.87999963760376,27.8000030517578 6.92999982833862,28.1550025939941 7.03999996185303,28.7800025939941 8.40062522888184,27.6990642547607 9.34500026702881,26.7775020599365 10.2399997711182,24.7600021362305 10.2799997329712,24.3200016021729 9.97749996185303,23.1150016784668 9.17999935150146,22.7200012207031z M9.03999996185303,18.8200016021729L9.49499988555908,19.1025009155273 9.96812534332275,19.6721897125244 10.5199995040894,20.7200012207031 11.1599998474121,22.6350021362305 11.3800001144409,24.5200023651123 11.188437461853,26.9756278991699 10.5675001144409,29.1500015258789 9.4478120803833,31.1293754577637 7.75999975204468,33 8.01000022888184,34.2675018310547 8.26000022888184,35.7000007629395 8.51999950408936,35.6600036621094 8.73999977111816,35.6600036621094 10.5046873092651,36.0125045776367 11.9224996566772,36.9800033569336 12.8940620422363,38.4275016784668 13.3199996948242,40.2200012207031 13.3199996948242,40.5 13.0853118896484,42.0806274414063 12.4025001525879,43.4550018310547 11.3034372329712,44.5668754577637 9.81999969482422,45.3600006103516 10.1674995422363,47.5149993896484 10.2784376144409,48.1106262207031 10.3199996948242,48.3199996948242 10.4399995803833,49.7000007629395 10.071249961853,51.6768760681152 9.15499973297119,52.9900016784668 7.97624969482422,53.7331275939941 6.81999969482422,54 6.35999965667725,54.0200004577637 4.38562488555908,53.6465644836426 3.23999977111816,52.7425003051758 2.70937490463257,51.6321868896484 2.57999968528748,50.6399993896484 2.61249971389771,50.1400032043457 2.65999960899353,49.9400024414063 3.48749995231628,48.3775024414063 5.07999992370605,47.7600021362305 5.98000001907349,47.9600028991699 6.86999988555908,48.7550010681152 7.15999984741211,49.9400024414063 7.15999984741211,50.0800018310547 6.97531223297119,50.8746910095215 6.50749969482422,51.4725036621094 5.23999977111816,52.1600036621094 4.73999977111816,52.2800025939941 4.55999994277954,52.4400024414063 4.73999977111816,52.6600036621094 6.39999961853027,53.060001373291 6.77999973297119,53.060001373291 7.296875,53.0031280517578 8.19999980926514,52.6350021362305 9.05812454223633,51.6593780517578 9.4399995803833,49.7800025939941 9.31999969482422,48.4200019836426 8.85999965667725,45.6400032043457 8.76000022888184,45.6400032043457 7.39999961853027,45.7200012207031 4.93343734741211,45.4071884155273 2.63749980926514,44.3575019836426 0.877812504768372,42.4040641784668 0.0200001019984484,39.3800010681152 1.21159985155828E-07,38.7800025939941 0.558125019073486,36.0762519836426 2.00499987602234,33.6650009155273 3.9993748664856,31.5012512207031 6.19999980926514,29.5400009155273 5.96000003814697,28.2800025939941 5.81999969482422,26.2600021362305 6.05593729019165,23.6968765258789 6.71749973297119,21.5050010681152 7.73531246185303,19.8306274414063 9.03999996185303,18.8200016021729z"/>


Very nice. I had a treble-clef, although not quite how I wanted it. So, after a quick, lazy google, I scaled using this suggestion (http://stackoverflow.com/questions/5109058/wpf-the-right-way-to-scale-a-path). Omitting the path data for clarity:

        <Path  Stroke="Black" Fill="Black" StrokeThickness="0.1" Data="..."
               Height="80" Width="30" Margin="0" RenderTransformOrigin="0, 0">
            <Path.RenderTransform>
                <ScaleTransform ScaleX="4.2" ScaleY="4.2"/>
            </Path.RenderTransform>
        </Path>

This pretty much gets me what I wanted. Now, I just need to do a simple routine to export all of the fonts I need into paths, put them into a separate XML file (or similar) and I can use them in Silverlight programmatically to render some nice graphics.


Hope this is of use to someone else struggling with the same questions. 

No comments:

Post a Comment