abc2svg
Check-in Differences
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From afc1222e0282b772 To d6636870ae43e9ac

2023-01-07
14:29
modules: nns: fix: add 'roman=2' to %%nns and fix some chords - reported by Robert Bley-Vroman ... (check-in: 8365017fa6 user: jef tags: trunk)
07:38
New release v1.22.6 ... (check-in: d6636870ae user: jef tags: trunk, v1.22.6)
07:35
source: copyright update ... (check-in: d49a8df607 user: jef tags: trunk)
2022-02-16
15:53
core: fix: bad setting of voice parameters in the tune header with possible crash - reported by Hudson Lacerda ... (check-in: 8a9b5595a4 user: jef tags: trunk)
2022-02-12
17:42
New release v1.21.6 ... (check-in: afc1222e02 user: jef tags: trunk, v1.21.6)
15:42
core: add !caesura!, !tick! and !short! ... (check-in: 21e5a0d63b user: jef tags: trunk)

Changes to README.md.
︙
12
13
14
15
16
17
18
19

20
21
22
23
24
25
26
12
13
14
15
16
17
18

19
20
21
22
23
24
25
26







-
+








The **abc2svg** core is based on the
[abcm2ps](https://github.com/lewdlime/abcm2ps) C code
which requires compilation on every operating system. 
The **abc2svg** scripts can run in any system with no compilation on
any platform that contains an internet browser. This includes MS-Windows,
Apple, and Unix-like systems (Linux, BSD...) as well as portable devices
such as cell phones and iPads.
such as cell phones and tablets.

A description of the ABC parameters that relate to both abcm2ps
and abc2svg can be found [here][1].

[1]: http://moinejf.free.fr/abcm2ps-doc/index.html "abc2svg documentation"

### 1. Web browser usage
︙
40
41
42
43
44
45
46
47

48
49
50
51
52
53
54
55
56

57
58
59
60
61
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
40
41
42
43
44
45
46

47
48
49
50
51
52
53
54
55

56
57
58
59
60
61
62
63
64
65
66
67
68

69
70
71
72
73
74
75
76







-
+








-
+












-
+







search on the internet the name of the browser and keywords such as
'bookmarklet' and 'javascript in url'). To edit the URL, extract
the javascript code
by right clicking on one of the bookmarklets below and selecting
copy url. Then, paste this code into the URL of your new bookmark
in your library.

To use a abc2svg bookmarklet, first load an ABC file into your
To use a abc2svg bookmarklet, first, load an ABC file into your
browser either from a web site of from a file on your system.
Once you see the textual abc code, click on the bookmarklet
that you created. After a slight delay depending upon the
complexity of the abc code, it should be replaced by a music
representation or a list of the contents of the ABC file. Here
are two bookmarklets that you can try.

This
<a href="javascript:(function(){var%20s,n=2,d=document,b=d.body;d.head.innerHTML='&lt;style&gt;svg{display:block};@media print{body{margin:0;padding:0;border:0};.nop{display:none}}&lt;/style&gt;\n';b.innerHTML='\n%25abc-2.2%25%3c!--\n'+b.textContent+'%25--%3e\n';function%20f(u){s=d.createElement('script');s.src='http://moinejf.free.fr/js/'+u;s.onload=function(){if(--n==0)dom_loaded()};d.head.appendChild(s)};f('abcweb-1.js');f('snd-1.js')})()"
<a href="javascript:(function(){var%20s,n=2,d=document,b=d.body;d.head.innerHTML='%3cstyle%3esvg{display:block};@media print{body{margin:0;padding:0;border:0};.nop{display:none}}%3c/style%3e\n';b.innerHTML='%3cscript type=%22text/vnd.abc%22%3e\n'+b.textContent+'%3c/script%3e\n';function%20f(u){s=d.createElement('script');s.src='http://moinejf.free.fr/js/'+u;s.onload=function(){if(--n==0)dom_loaded()};d.head.appendChild(s)};f('abcweb-1.js');f('snd-1.js')})()"
title="Copy me">first abc2svg bookmarklet</a>
renders all the music it finds in the page currently displayed.  
Once the music is displayed, clicking inside a tune starts playing it
from the beginning.
Clicking on a particular note or rest starts playing from that point.  
To print or convert the music to a PDF file,
simply click on the 'Print' button of the web browser.
(If the 'Print' button does not appear on your browser menu,
try right clicking on the web page.)

Alternatively, if your source contains many tunes, you can
use this
<a href="javascript:(function(){var%20s,n=2,d=document,b=d.body;d.head.innerHTML='&lt;style&gt;svg{display:block};@media print{body{margin:0;padding:0;border:0};.nop{display:none}}&lt;/style&gt;\n';b.innerHTML='\n%25abc-2.2%25%3c!--\n'+b.textContent+'%25--%3e\n';function%20f(u){s=d.createElement('script');s.src='http://moinejf.free.fr/js/'+u;s.onload=function(){if(--n==0)dom_loaded()};d.head.appendChild(s)};f('abcweb1-1.js');f('snd-1.js')})()"
<a href="javascript:(function(){var%20s,n=2,d=document,b=d.body;d.head.innerHTML='%3cstyle%3esvg{display:block};@media print{body{margin:0;padding:0;border:0};.nop{display:none}}%3c/style%3e\n';b.innerHTML='%3cscript type=%22text/vnd.abc%22%3e\n'+b.textContent+'%3c/script%3e\n';function%20f(u){s=d.createElement('script');s.src='http://moinejf.free.fr/js/'+u;s.onload=function(){if(--n==0)dom_loaded()};d.head.appendChild(s)};f('abcweb1-1.js');f('snd-1.js')})()"
title="Copy me">second bookmarklet</a>.
The browser will list the titles of the tunes.
Clicking on a title displays the tune.
Playing and printing work in the same manner as above.  
The generated pages contain a yellow menu in the top right corner which permits
you to return to the tune list or to modify the music. With this last option,
you can adjust the page size before printing, correct the ABC syntax or
︙
105
106
107
108
109
110
111













112
113
114
115
116
117
118
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131







+
+
+
+
+
+
+
+
+
+
+
+
+







If your ABC files contain `%%abc-include`, then you must:

  - load the ABC file using the browse button,
  - click the include file name button,
  - and load the include file using the same previous browse button.  

Only one included file is allowed.

If you have installed abc2svg in a site of yours, you may put a file
**pref.js** with the abc2svg scripts. This file may contain various
javascript functions and also ABC parameters.
These parameters must be defined inside the following sequence:

	if (typeof abc2svg == "undefined")    
	    var abc2svg = {}
	if (!abc2svg.a_inc)
		abc2svg.a_inc = {}
	abc2svg.a_inc["default.abc"] = `
	.. put here the ABC parameters ..
	`

If you have a US keyboard, its behaviour can be changed for easier music
entering by one of these bookmarklets:
<a href="javascript:(function(){if(typeof%20abc2svg.loadjs=='function'){abc2svg.loadjs('abckbd-1.js')}else{alert('use%20with%20abc2svg%20editor')}})()"
title="Copy me">keyboard 1</a>
and
<a href="javascript:(function(){if(typeof%20abc2svg.loadjs=='function'){abc2svg.loadjs('abckbd2-1.js')}else{alert('use%20with%20abc2svg%20editor')}})()"
︙
132
133
134
135
136
137
138
139

140
141
142
143


144
145
146
147
148
149
150
151
152
153
154
155
156
157


158
159
160

161

162
163
164
165
166
167

168
169
170
171
172
173
174
145
146
147
148
149
150
151

152
153
154


155
156
157
158
159
160
161
162
163
164
165
166
167
168


169
170
171
172
173
174

175

176
177
178
179

180
181
182
183
184
185
186
187







-
+


-
-
+
+












-
-
+
+



+
-
+
-




-
+







(Note that, in the example, the paths are relative - see why below.)

[4]: http://moinejf.free.fr/abcm2ps-doc/tabac.html "J'ai du bon tabac"

As it is apparent, HTML and ABC can be mixed in the same html file.
Both are rendered in the order you defined them.

You may also have noticed this CSS about SVG elements:  
You may also have noticed this style about SVG elements:  
`        svg { display: block }`

This permits to have the music on separate vertical areas.  
Without this CSS, the music is in-lined as in [this other example][5].
It puts the lines of music on vertical areas.  
Without this style, the music is in-lined as in [this other example][5].

[5]: http://moinejf.free.fr/abcm2ps-doc/dansou-i.html "Tune index example"

Global ABC parameters may be added as `parameter=value` in the query string
of the URL of the page. The [following example][16] calls the same
"J'ai du bon tabac" with `pagescale=1.2` (giving `%%pagescale 1.2`).

[16]: http://moinejf.free.fr/abcm2ps-doc/tabac.html?pagescale=1.2 "J'ai du bon tabac"

In the above examples, all the ABC music is generated (displayed
and ready to be played) by means of the script **abcweb-1.js**.  
If there is a large collection of tunes, it may be preferable to
link to the other script, **abcweb1-1.js**, which permits you
to select particular tune(s). Without an explicit
link to the other script, **abcweb1-1.js**, which offers a tune selection.
Without an explicit
selection (see below), a list of the tunes is displayed, and,
after a tune has been selected, the whole page is replaced by the music.
(The HTML code, if any, is not displayed.)
You can go back to the list of the tunes thanks to the menu
A menu on the top right corner of the screen
on the top right corner of the screen. Here is [a real example][6].
permits to get the tune list again. Here is [a real example][6].

[6]: http://moinejf.free.fr/abc/boyvin-2-2.html "J. Boyvin organ tunes"

As you may notice in the menu, the edition of the ABC content is proposed.
This permits, for example, to transpose the tunes.
This permits you, for example, to transpose the tunes.
This edition is done inside the browser, so, your changes will be lost
after leaving the page.

When accessing such pages, an external selection of the tunes
can be accomplished by ending the URL with the character '`#`'
followed by a [regular expression][7]
to be applied to the tune headers.
︙
187
188
189
190
191
192
193








194
195
196
197
198
199
200
201
202
203
204
205

206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223

224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243

244
245
246
247
248
249
250
251






252
253
254
255
256
257
258







+
+
+
+
+
+
+
+











-
+

















-
+







-
-
-
-
-
-







#### 1.4 Installing abc2svg on your system or on your server

The **abc2svg** package on my server is still being tuned
which could change its behaviour at any unknown time; so you may
prefer to install and run it from your own system. 

There are many ways to install abc2svg:

- [tarball][16] or [.zip archive][17] from my site  
  Both files contain the scripts that are generated from the source
  when abc2svg is stable enough. The scripts are ready
  to be used from the root directory.

[16]: http://moinejf.free.fr/abc2svg.tar.bz2 "tarball"
[17]: http://moinejf.free.fr/abc2svg.zip "ZIP file"

- [Guido Gonzato's page](http://abcplus.sourceforge.net/#abc2svg)  
  Guido maintains a ZIP archive of the abc2vg scripts after
  each release of a new version (many thanks, Guido!).  
  You can just download and unzip this archive. **abc2svg** should
  run immediately in your machine without connecting to my site.  
  In the same page, you can also find some binaries of QuickJS,
  a fast javascript interpreter (see below).

- tarball  
  From the timeline in the [chisel repository][13], you can get a tarball
  any version of the abc2svg source and install it in your system.  
  of any version of the abc2svg source and install it in your system.  
  The abc2svg scripts must then be built from the raw source files
  described in the section 'Build' below.  
  The disadvantage of this approach is that if you want
  to use an other or newer version you need to download a new tarball.

[13]: https://chiselapp.com/user/moinejf/repository/abc2svg/timeline

- fossil clone  
  If you can get the fossil program (one binary) for your system,
  you may clone the chisel repository by  
  `        fossil clone https://chiselapp.com/user/moinejf/repository/abc2svg abc2svg.fossil`  
  `        fossil open abc2svg.fossil`  
  and get the abc2svg source files containing the last changes
  between the official versions. Updating your files is done by  
  `        fossil pull`  
  `        fossil update`  
  Building the scripts is done in the same way as with a tarball.  
  The repository is presently over 34Mb.
  The repository is presently over 50Mb.

  For those unfamiliar with [fossil][14], it is an integrated
  software management system similar to [git](https://git-scm.com/).  
  Chisel acts like a repository similar to [github](https://github.com/).

[14]: https://fossil-scm.org/home/doc/trunk/www/index.wiki

- npm  
  The scripts may be installed from the `npm` repository by:  
  `        npm install abc2svg`  
  They are ready to run (web and shell with nodejs).
  I upload a new npm version about once a month.

Using bookmarklets with a local installation does not work directly
because of a cross-domain security hole, but this is possible by running
a local HTTP server (you will also have to change the location
of the scripts in the bookmarklet code).

If you have write access on a remote server, you may put there
the abc2svg scripts. There is no automatic process to do that.
︙
258
259
260
261
262
263
264
265
266


267
268
269

270
271
272
273
274
275
276
277
278
279
273
274
275
276
277
278
279


280
281
282
283

284
285
286

287
288
289
290
291
292
293







-
-
+
+


-
+


-








#### 2.1 abc2svg shell scripts

As you have seen, printing the music can be done easily with any web browser.
You can automate the process of creating music sheets
with **abc2svg** using shell scripts running a Javascript interpreter.

The interfaces to the various scripts are different. Below you will
find various scripts that I had to built.
The interfaces to the various interpreters are different. Below you will
find the scripts I had to built.

- `abcqjs` with `qjs` [QuickJS by Fabrice Bellard and Charlie Gordon][10]
- `abcmjs` with `js78`, `js68`, `js60`, `js52` or `js24` (Mozilla JavaScript shell)
- `abcmjs` with `js78`, `js60`, `js52` or `js24` (Mozilla JavaScript shell)
- `abcjsc` with `jsc` (webkit2gtk)
- `abcnode` with `node` (nodeJS without module)
- `abc2svg` with `node` (nodeJS with modules)

[10]: https://bellard.org/quickjs/

Each script gets the abc2svg options and ABC files from the command line
and sends the generated file to `stdout` and possible errors to `stderr`.  
The general syntax of the command line is:  
`        script [script.js] [options] ABC_file [[options] ABC_file]* [options]`
︙
334
335
336
337
338
339
340
341

342
343
344
345
346
347
348


349
350
351
352
353
354
355
356
348
349
350
351
352
353
354

355

356





357
358

359
360
361
362
363
364
365







-
+
-

-
-
-
-
-
+
+
-







  This script just outputs the abc2svg parameters.

#### 2.3 PDF generation

`abctopdf` is a shell script which converts ABC to PDF using one of the
previous shell scripts and, either a chrome/chromium compatible web browser
(settable by the environment variable 'BROWSER'),
or the program [weasyprint](https://weasyprint.org/) or
or the program [weasyprint](https://weasyprint.org/).
the program `rsvg-convert`.

With `rsvg-convert`, the used music font must be installed and defined by
`%%musicfont <fontname>`.

Note also that, with `weasyprint` or `rsvg-convert`, the paper size is
forced to A4. Instructions for changing this size may be found in the
Note also that, with `weasyprint`, the paper size is forced to A4.
Instructions for changing this size may be found in the script source.
script source.

The output PDF document may be specified by the command line argument `-o`
(default `abc.pdf`).

Example:  
`        abctopdf my_file.abc -o my_file.pdf`

︙
422
423
424
425
426
427
428
429

430
431
432
433
434
435
436

437

438
439
440
441
442
443



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460

461
462
463
464
465
466
467
468
469
















470
471
472
473
474
475






















476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493

494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
431
432
433
434
435
436
437

438
439
440
441
442
443
444
445
446

447
448
449
450
451


452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470

471
472
473
474
475
476
477



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538

539
540
541
542
543







544
545
546
547
548
549
550







-
+







+
-
+




-
-
+
+
+
















-
+






-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

















-
+




-
-
-
-
-
-
-








- The music is displayed as SVG images. There is one image per
  music line / text block.  
  If you want to move these images to some other files,
  each one must contain the full CSS and defs. For that, insert  
  `        %%fullsvg x`  
  in the ABC file before rendering (see the
  [fullsvg documentation](http://moinejf.free.fr/abcm2ps-doc/fullsvg.xhtml)
  [fullsvg documentation](http://moinejf.free.fr/abcm2ps-doc/fullsvg.html)
  for more information).

- Playing uses the HTML5 audio and/or the midi APIs.  
  For audio, by default, abc2svg uses a sound font (format SF2)
  which is split into one file
  per instrument. This sound font is stored in the subdirectory `Scc1t2/`.
  Each instrument file is a base64 encoded javascript array.  

  Other sound fonts may be used. Some of them are stored in the subdirectory
	Other sound fonts may be used. Some of them are stored in the subdirectory
  `sf2/` (`AWE_ROM_gm` and `2MBGMGS`). Two formats are supported: raw SF2 and
  SF2 wrapped into javascript (the raw SF2 files can be loaded
  only when they are in the same HTTP domain).
  The shell script `sf.sh` (in `sf2/`) may be used to create the javacript files
  from raw SF2 files.  
  The sound font to be used for playing may be defined in the ABC code
  from raw SF2 files.

	The sound font to be used for playing may be defined in the ABC code
  by the command `%%soundfont`. E.g.:  
  `        %%soundfont http://moinejf.free.fr/js/sf2/AWE_ROM_gm.js`

- The names of the abc2svg scripts have a suffix which is the version of
  the core interface (actually '`-1`').

#### 4.3 More about the web scripts

Here are the scripts which are used in a web context:

- `abcweb-1.js`  
  This script replaces the ABC or MEI sequences found in the (X)HTML file
  by SVG images of the music.  
  The ABC sequences are searched:
  - first inside &lt;script&gt; elements with a type
    ["text/vnd.abc"](https://www.iana.org/assignments/media-types/text/vnd.abc)
    (the script tag is removed from the page),
    (the script tag is replaced by a &lt;div&gt;),
  - or inside (X)HTML elements with a class `abc` (lower case letters),
  - otherwise on `X:` or `%abc-` at start of line up to a XML tag at start of line.

	When a ABC sequence is not included in a &lt;script&gt; and when it contains
  the characters '<', '>' or '&',
  it must be enclosed in a XML comment or in a CDATA
  (%&lt;![CDATA[ .. %]]&gt; - the comment or CDATA must be in a ABC comment).  
  See the
  [%%beginml documentation](http://moinejf.free.fr/abcm2ps-doc/beginml.xhtml)
  (%&lt;![CDATA[ .. %]]&gt; - the comment or CDATA must be in a ABC comment).

	When using &lt;script&gt;, it is possible to set abc2svg parameters via CSS.
  For that, the &lt;style&gt; in the HTML &lt;head&gt; may contain custom
  properties (properties starting with '--') and these properties are converted
  to abc2svg parameters (starting with '%%') before the ABC sequence.  
  For instance, in the (&lt;head&gt;) &lt;style&gt; element, you can put:  
  `        .parm {--pagewidth:30cm;  --bgcolor : yellow}`

	and in some &lt;script ..vnd.abc..&gt;, you set the class:  
  `        <script class="parm" type="text/vnd.abc">`

	This defines the page width and the background color of the generated music.

	See the
  [%%beginml documentation](http://moinejf.free.fr/abcm2ps-doc/beginml.html)
  for an example, and here is [how to put inline music in HTML][15].   
  Playing and highlighting the played notes may be offered loading
  the script `snd-1.js` (see below).

[15]: http://moinejf.free.fr/abcm2ps-doc/inline.html "abc2svg - inline music"

    This script also accepts a parameter `with_source`.
    When this parameter is set, the music source is included before the
    SVG images of the music. An argument `nohead` prevents displaying
    the source of the first music sequence. The music source is displayed
    in a &lt;pre&gt; element of class `source`. The SVG's are included
    in a &lt;div&gt; of the same class `source`.
    The source may be displayed either above (default) or
    at the left side of the music (using a style as
    `.source{display: inline-block; vertical-align: top}`).
    See the source of
    [abcm2ps/abc2svg features](http://moinejf.free.fr/abcm2ps-doc/features.html)
    for an example.

    The music source may be editable.
    To change it, the script contains two functions:  
    - `abc2svg.get_music` returns the source of the music sequence
       (in &lt;script&gt; type "text/vnd.abc", class="abc" or inlined ABC)
       Its argument is the HTML &lt;div&gt; element that contains the music.  
    - `abc2svg.set_music` has two arguments, the HTML &lt;div&gt; element
       and the new source of the music.
       It generates and replaces the music in the &lt;div&gt;.

- `abcweb1-1.js`  
  The page body is analyzed as a ABC file and its content is
  replaced by music as SVG images.  
  If the page contains reserved XML characters ('&lt;', '&gt;' and '&amp;'),
  the ABC code must be enclosed in a
  `         <script type="text/vnd.abc"> .. </script>`
  sequence.

	When there are many tunes in the file, the script displays a list
  of the tunes. The list step may be bypassed when the URL of the file
  contains a regular expression in the 'hash' value ('#' followed by
  a string at the end of the URL).
  This string does a
  [%%select](http://moinejf.free.fr/abcm2ps-doc/select.xhtml).

	When one or many tunes are displayed, a menu in the top/right corner
  offers to go back to the tune list or to modify the ABC source.
  

- `snd-1.js`  
  This script may be used with `abcweb-1.js` or `abcweb1-1.js`
  to play the rendered ABC music.  

- `abcdoc-1.js`  
  This script is mainly used for ABC documentation.
  It lets the ABC source sequences in the page before the SVG images.  
  See the source of
  [abcm2ps/abc2svg features](http://moinejf.free.fr/abcm2ps-doc/features.html)
  for an example.

### 5. MEI support

As an experimental feature, an extended core `mei2svg-1.js` may be generated.
This one may handle both the ABC and
[MEI](https://music-encoding.org/ "Music Encoding Initiative")
notations.

︙
Deleted abc2svg.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68




































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/usr/bin/env node

// abc2svg with native nodeJS

// define the system functions
    var	sep = require('path').sep

// load the abc2svg core
var m = require("abc2svg/abc2svg-1");
global.abc2svg = m.abc2svg;
global.Abc = m.Abc;

// interpreter specific functions
abc2svg.print = function(str) {
	process.stdout.write(str + '\n')
}
abc2svg.printErr = function(str) {
	process.stderr.write(str + '\n')
}
abc2svg.quit = function() {
	process.exit(1)
}
abc2svg.readFile = function(fname) {
    var	fs = require("fs")

	try {
		return fs.readFileSync(fname).toString()
	} catch(e) {
	}
	return null
}

abc2svg.get_mtime = function(fn) {
    var	fs = require("fs")

	return fs.statSync(fn).mtime
}

abc2svg.loadjs = function(fn, relay, onerror) {
	try {
		var geval = eval;
		geval(abc2svg.readFile(__dirname + sep + fn))
		if (relay)
			relay()
	} catch(e) {
		if (onerror)
			onerror()
		else
			abc2svg.printErr('Cannot read file ' + fn)
	}
}

// load the command line
m = require("abc2svg/cmdline");
global.user = m.user;
global.abc_cmd = m.abc_cmd

// set the ABC path
var path = process.env.ABCPATH
if (path)
	abc2svg.path = path.split(':')

// main
var args = process.argv
args.shift();
args.shift()

abc_cmd("abc2svg", args, "nodeJS")
Changes to abc2svg.ttf.

cannot compute difference between binary files

Deleted abcjs24.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38






































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/usr/bin/env js24
// abc2svg with js24 (Mozilla JavaScript shell - Spidermonkey
//		packages: debian: libmozjs-24-bin, voidlinux: mozjs24)

// interpreter specific functions
var abc2svg = {
    print: function(str) {
	print(str)
    },
    printErr: function(str) {
	printErr(str)
    },
    quit: function() {
	quit(1)
    },
    readFile: function(fname) {
	return read(fname)
    },
    loadjs: function(fn, relay, onerror) {
	try {
		loadRelativeToScript(fn)
		if (relay)
			relay()
	} catch(e) {
		if (onerror)
			onerror()
		else
			printErr('Cannot read file ' + fn)
	}
    } // loadjs()
} // abc2svg

loadRelativeToScript("abc2svg-1.js")
loadRelativeToScript("cmdline.js")

// main

	abc_cmd("abcjs24", scriptArgs, "js24 (SpiderMonkey)")
Deleted abcjs52.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38






































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/usr/bin/env js52
// abc2svg with js52 (Mozilla JavaScript shell - Spidermonkey
//		packages: debian: libmozjs-52-dev, voidlinux: mozjs52)

// interpreter specific functions
var abc2svg = {
    print: function(str) {
	print(str)
    },
    printErr: function(str) {
	printErr(str)
    },
    quit: function() {
	quit(1)
    },
    readFile: function(fname) {
	return read(fname)
    },
    loadjs: function(fn, relay, onerror) {
	try {
		loadRelativeToScript(fn)
		if (relay)
			relay()
	} catch(e) {
		if (onerror)
			onerror()
		else
			printErr('Cannot read file ' + fn)
	}
    } // loadjs()
} // abc2svg

loadRelativeToScript("abc2svg-1.js")
loadRelativeToScript("cmdline.js")

// main

	abc_cmd("abcjs52", scriptArgs, "js52 (SpiderMonkey)")
Deleted abcjs60.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38






































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/usr/bin/env js60
// abc2svg with js60 (Mozilla JavaScript shell - Spidermonkey
//		packages: debian: libmozjs-60-dev, voidlinux: mozjs60)

// interpreter specific functions
var abc2svg = {
    print: function(str) {
	print(str)
    },
    printErr: function(str) {
	printErr(str)
    },
    quit: function() {
	quit(1)
    },
    readFile: function(fname) {
	return read(fname)
    },
    loadjs: function(fn, relay, onerror) {
	try {
		loadRelativeToScript(fn)
		if (relay)
			relay()
	} catch(e) {
		if (onerror)
			onerror()
		else
			printErr('Cannot read file ' + fn)
	}
    } // loadjs()
} // abc2svg

loadRelativeToScript("abc2svg-1.js")
loadRelativeToScript("cmdline.js")

// main

	abc_cmd("abcjs60", scriptArgs, "js60 (SpiderMonkey)")
Changes to abcnode.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

27
28


29
30
31
32
33
34
35
1
2
3
4
5
6
7
8
9
10
11
12














13


14
15
16
17
18
19
20
21
22












-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+







:;# abc2svg with raw nodeJS

# search where are the abc2svg scripts
p=$0
if [ `dirname $p` = . ]; then
	p=`which $p`
fi
if [ -L $p ]; then
	p=`realpath $p`
fi
p=`dirname $p`

cat ${p}/abc2svg-1.js ${p}/cmdline.js > /tmp/toto.js
case $1 in
	*.js)
		cat $1 >> /tmp/toto.js
		if grep 'abc2svg.abc_init =' "$1" > /dev/null; then
			x="no"
		fi
		shift
		;;
esac
if [ "x$x" != 'xno' ]; then
	cat ${p}/tohtml.js >> /tmp/toto.js
fi

node - "$@" <<EOF
cat <<EOF >> /tmp/toto.js
    var	path='${p}/'
    var	path='${p}/',
	abc2svg = {}

// interpreter specific functions
abc2svg.print = function(str) {
	process.stdout.write(str + '\n')
}
abc2svg.printErr = function(str) {
	process.stderr.write(str + '\n')
︙
63
64
65
66
67
68
69
70
71
72
73
74
75
76

77
78
79


80
81




82



83
84
85
86
50
51
52
53
54
55
56




57
58
59
60
61
62
63
64
65


66
67
68
69
70
71
72
73
74
75









-
-
-
-



+



+
+
-
-
+
+
+
+

+
+
+


-
-
		if (onerror)
			onerror()
		else
			abc2svg.printErr('Cannot read file ' + fn)
	}
}

// (this file is wrapped)
global.Abc = Abc
global.abc2svg = abc2svg

// main
    var	args = process.argv,
	i = '${ABCPATH}'

	if (i)
		abc2svg.path = i.split(':')

	abc2svg.loadjs("abc2svg-1.js")
	abc2svg.loadjs("cmdline.js")
	args.shift()
	args.shift()

	global.Abc = Abc
	global.abc2svg = abc2svg

	global.user = user;		// (for modules/page.js)

	args.shift()			// (remove 'node')
	args.shift()			// (remove the '-')
	abc_cmd("abcnode", args, "nodeJS")
EOF

node /tmp/toto.js "$@"
Changes to abctopdf.
︙
37
38
39
40
41
42
43
44

45
46
47
48
49
50
51
52
53
54
55




56
57
58
59
60
61
62
63
64
37
38
39
40
41
42
43

44
45
46
47
48
49
50
51
52



53
54
55
56
57

58
59
60
61
62
63
64







-
+








-
-
-
+
+
+
+

-







	if [ $bdir != '.' ]; then
		bdir="$bdir/"
	else
		bdir=''
	fi
fi
# (sorted from fastest to slowest in ARM machines)
for c in qjs jsc js24 js52 js60 d8 abc2svg node end; do
for c in qjs jsc js78 js60 js52 js24 d8 node end; do
	if [ $c = 'end' ]; then
		echo "$0: could not find a javascript interpreter - abort"
		exit 1
	fi
	if command -v $c >/dev/null 2>&1; then
		case $c in
		(qjs) c=abcqjs;;
		(jsc) c=abcjsc;;
		(js24) c=abcjs24;;
		(js52) c=abcjs52;;
		(js60) c=abcjs60;;
		(js24) c=abcmjs;;
		(js52) c=abcmjs;;
		(js60) c=abcmjs;;
		(js78) c=abcmjs;;
		(d8) c=abcv8;;
		(abc2svg) c=abc2svg;;
		(node) c=abcnode;;
		esac
		if command -v $c >/dev/null 2>&1; then
			abcscr=$c
			break
		fi
		if command -v $bdir$c >/dev/null 2>&1; then
︙
Changes to build.
1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


29
30
31
32
33
34
35
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


28
29
30
31
32
33
34
35
36









+

















-
-
+
+







:;# simple build of abc2svg as a shell script

MAJOR=1

# use jsmin if available
if command -v jsmin >/dev/null 2>&1; then
# binary program
gen() {
 cat "$@" | jsmin
 echo ""
}
elif command -v qjs >/dev/null 2>&1; then
# script using QuickJS
if [ ! -f jsmin-min.js ] || [ jsmin-min.js -ot jsmin.js ]; then
	qjs --std jsmin.js < jsmin.js > jsmin-min.js
fi
gen() {
 cat "$@" | qjs --std jsmin-min.js
}
else
# no minify
gen() {
 cat "$@"
}
fi

out=abc2svg-$MAJOR.js
VERSION=`grep '"version"' package.json|cut -d'"' -f4`
DATE=`date -r package.json '+%Y-%m-%d'`
VERSION='??????'
DATE=`date -Idate`
cp core/license $out
gen core/abc2svg.js core/deco.js core/draw.js\
 font.js core/format.js core/front.js core/music.js core/parse.js core/subs.js\
 core/svg.js core/tune.js core/lyrics.js core/gchord.js core/tail.js\
 core/modules.js >> $out
 echo "abc2svg.version=\"$VERSION\";abc2svg.vdate=\"$DATE\"" >> $out

︙
64
65
66
67
68
69
70

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87


88
89
90
91
92
93
94
65
66
67
68
69
70
71
72
73
74
75
76



77
78








79
80
81
82
83
84
85









+




-
-
-


-
-
-
-
-
-
-
-
+
+





-
-
gen modules/perc.js > perc-$MAJOR.js
gen modules/wps.js modules/psvg.js > psvg-$MAJOR.js
gen modules/roman.js > roman-$MAJOR.js
gen modules/soloffs.js > soloffs-$MAJOR.js
gen modules/sth.js > sth-$MAJOR.js
gen modules/strtab.js > strtab-$MAJOR.js
gen modules/temper.js > temper-$MAJOR.js
gen modules/tropt.js > tropt-$MAJOR.js

cp editor/edit.css edit-$MAJOR.css

gen util/abcdoc.js > abcdoc-$MAJOR.js
gen util/abcemb.js > abcemb-$MAJOR.js
gen util/abcemb1.js > abcemb1-$MAJOR.js
gen util/abcemb2.js > abcemb2-$MAJOR.js
gen util/abcweb.js > abcweb-$MAJOR.js
gen util/abcweb1.js > abcweb1-$MAJOR.js
gen util/abcweb.js > abcweb2-$MAJOR.js
gen util/chord.js > chord-$MAJOR.js
gen util/json.js > json-$MAJOR.js
gen util/toaudio.js > toaudio-$MAJOR.js
gen util/play.js util/toaudio.js util/toaudio5.js\
 util/sf2-parser.js util/tomidi5.js util/follow.js > play-$MAJOR.js
gen util/play.js util/sndgen.js util/sndaud.js\
 util/sf2-parser.js util/sndmid.js util/follow.js > snd-$MAJOR.js
gen util/play.js util/sndgen.js util/sndaud.js util/sf2-parser.js\
 util/sndmid.js util/follow.js util/chord.js > snd-$MAJOR.js

sed "s+@MAJOR@+$MAJOR+" editor/edit.xhtml > edit-$MAJOR.xhtml
sed "s+@MAJOR@+$MAJOR+" editor/abckbd.js > abckbd-$MAJOR.js
sed "s+@MAJOR@+$MAJOR+" editor/abckbd2.js > abckbd2-$MAJOR.js
sed "s+@MAJOR@+$MAJOR+" editor/edit.js > edit-$MAJOR.js

touch follow-$MAJOR.js
Changes to build.ninja.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


29
30
31
32

33

34
35

36
37
38
39
40

41



42


43
44
45
46
47

48
49
50
51
52
53

54

55




56
57
58
59
60
61
62
1
2
3
4
5
6
7



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22



23
24




25

26
27

28
29
30
31
32
33
34
35
36
37
38

39
40
41
42
43
44

45
46
47
48
49
50
51
52
53
54

55
56
57
58
59
60
61
62
63
64
65







-
-
-















-
-
-
+
+
-
-
-
-
+
-
+

-
+





+

+
+
+
-
+
+




-
+






+

+
-
+
+
+
+







# rules for ninja (ninja-build) or samurai

# minifying with 'jsmin' or 'uglifyjs' may be disabled by running
#	NOMIN=1 ninja -v

MAJOR=1

# ninja tool
tool=samu

rule cp
  command = cp $in $out

rule cpm
  command = sed "s+@MAJOR@+$MAJOR+" $in > $out

rule font
  command = fontforge -c 'open("font/abc2svg.sfd").generate("abc2svg.ttf")';$
   fontforge -c 'open("font/abc2svg.sfd").generate("abc2svg.woff")';$
   echo '// abc2svg music font' > font.js;$
   echo "var musicfont = 'url(\"data:application/octet-stream;base64,\\" >> font.js;$
   base64 abc2svg.ttf | sed 's/$$/\\/' >> font.js;$
   echo "\") format(\"truetype\")'" >> font.js

rule jsc
  command = VERSION=`grep '"version"' package.json|cut -d'"' -f4`;$
   if [ -e .fslckout ] && command -v fossil >/dev/null 2>&1; then $
	c=`fossil info | grep checkout:`;$
  command = if [ -e .fslckout ] && command -v fossil >/dev/null 2>&1; then $
	v=`fossil describe`;$
	t=`fossil info | grep tags: | awk '{print $$NF}'`;$
	if [ "$${t#v}" != "$$VERSION" ]; then $
		VERSION="$${VERSION}-$$(echo $$c | cut -d' ' -f2 | cut -c1-10)";$
	fi;$
	d=`date -Idate`;$
	DATE=`echo $$c | cut -d' ' -f3`;$
	v="abc2svg.version=\"$$v\";abc2svg.vdate=\"$$d\"";$
   else $
	DATE=`date -r package.json '+%Y-%m-%d'`;$
	v=`grep abc2svg.version $out`;$
   fi;$
   cp core/license $out;$
   if [ "x$$NOMIN" = "x" ]; then $
      if command -v jsmin >/dev/null 2>&1; then $
	cat $in | jsmin >> $out;$
	echo "" >> $out;$
      else $
	if command -v qjs >/dev/null 2>&1; then $
	  cat $in | ./jsmin >> $out;$
	else $
	cat $in | uglifyjs >> $out;$
	  cat $in | uglifyjs >> $out;$
	fi;$
     fi;$
   else $
	cat $in >> $out;$
   fi;$
   echo "abc2svg.version=\"$$VERSION\";abc2svg.vdate=\"$$DATE\"" >> $out
   echo $$v >> $out

rule jscu
  command = cp core/license $out;$
   if [ "x$$NOMIN" = "x" ]; then $
      if command -v jsmin >/dev/null 2>&1; then $
	cat $in | jsmin >> $out;$
	echo "" >> $out;$
      else $
	if command -v qjs >/dev/null 2>&1; then $
	uglifyjs $in >> $out;$
	  cat $in | ./jsmin >> $out;$
	else $
	  cat $in | uglifyjs >> $out;$
	fi;$
      fi;$
   else $
	cat $in >> $out;$
   fi

rule edit
  command = if [ "x$$NOMIN" = "x" ]; then $
︙
85
86
87
88
89
90
91
92


93
94
95
96
97
98
99
88
89
90
91
92
93
94

95
96
97
98
99
100
101
102
103







-
+
+








build font: font | font/abc2svg.sfd
build sf: sf | Scc1t2/

build abc2svg-$MAJOR.js: jsc core/abc2svg.js core/deco.js core/draw.js $
 font.js core/format.js core/front.js core/music.js core/parse.js core/subs.js $
 core/svg.js core/tune.js core/lyrics.js core/gchord.js core/tail.js $
 core/modules.js | package.json
 core/modules.js
#| package.json
build mei2svg-$MAJOR.js: jsc core/abc2svg.js core/deco.js core/draw.js $
 font.js core/format.js core/front.js core/music.js core/parse.js core/subs.js $
 core/svg.js core/tune.js core/lyrics.js core/gchord.js core/mei.js core/tail.js $
 core/modules.js | package.json

build ambitus-$MAJOR.js: jscu modules/ambitus.js
build break-$MAJOR.js: jscu modules/break.js
︙
118
119
120
121
122
123
124

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141


142
143
144
145
146
147
148
149
150
151


152
153
154
155
156
157

158
159
160


161
162
163
164
165

166
167
168
169
170
171























172
173
174
175
176
177
178
122
123
124
125
126
127
128
129
130
131
132
133



134
135


136





137
138
139
140
141
142
143
144
145



146
147
148
149
150
151
152

153
154


155
156
157
158
159
160

161
162





163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187












+




-
-
-


-
-

-
-
-
-
-
+
+







-
-
-
+
+





-
+

-
-
+
+




-
+

-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
-
-
-
build perc-$MAJOR.js: jscu modules/perc.js
build psvg-$MAJOR.js: jscu modules/wps.js modules/psvg.js
build roman-$MAJOR.js: jscu modules/roman.js
build soloffs-$MAJOR.js: jscu modules/soloffs.js
build sth-$MAJOR.js: jscu modules/sth.js
build strtab-$MAJOR.js: jscu modules/strtab.js
build temper-$MAJOR.js: jscu modules/temper.js
build tropt-$MAJOR.js: jscu modules/tropt.js

build edit-$MAJOR.css: cp editor/edit.css

build abcdoc-$MAJOR.js: jscu util/abcdoc.js
build abcemb-$MAJOR.js: jscu util/abcemb.js
build abcemb1-$MAJOR.js: jscu util/abcemb1.js
build abcemb2-$MAJOR.js: jscu util/abcemb2.js
build abcweb-$MAJOR.js: jscu util/abcweb.js
build abcweb1-$MAJOR.js: jscu util/abcweb1.js
build abcweb2-$MAJOR.js: jscu util/abcweb.js
build chord-$MAJOR.js: jscu util/chord.js
build json-$MAJOR.js: jscu util/json.js
build toaudio-$MAJOR.js: jscu util/toaudio.js
build play-$MAJOR.js: jscu util/play.js util/toaudio.js $
 util/toaudio5.js util/sf2-parser.js util/tomidi5.js util/follow.js
build snd-$MAJOR.js: jscu util/play.js util/sndgen.js $
 util/sndaud.js util/sf2-parser.js util/sndmid.js util/follow.js
build snd-$MAJOR.js: jscu util/play.js util/sndgen.js util/sndaud.js $
 util/sf2-parser.js util/sndmid.js util/follow.js util/chord.js

build edit-$MAJOR.xhtml: cpm editor/edit.xhtml
build abckbd-$MAJOR.js: edit editor/abckbd.js
build abckbd2-$MAJOR.js: edit editor/abckbd2.js
build edit-$MAJOR.js: edit editor/edit.js

build all: phony abc2svg-$MAJOR.js abcdoc-$MAJOR.js $
 abcemb-$MAJOR.js abcemb1-$MAJOR.js abcemb2-$MAJOR.js $
 abcweb-$MAJOR.js abcweb1-$MAJOR.js abcweb2-$MAJOR.js chord-$MAJOR.js $
 json-$MAJOR.js toaudio-$MAJOR.js snd-$MAJOR.js play-$MAJOR.js psvg-$MAJOR.js $
 abcweb-$MAJOR.js abcweb1-$MAJOR.js $
 snd-$MAJOR.js psvg-$MAJOR.js $
 ambitus-$MAJOR.js break-$MAJOR.js capo-$MAJOR.js $
 chordnames-$MAJOR.js clair-$MAJOR.js clip-$MAJOR.js $
 combine-$MAJOR.js diag-$MAJOR.js equalbars-$MAJOR.js gamelan-$MAJOR.js $
 grid-$MAJOR.js grid2-$MAJOR.js grid3-$MAJOR.js jianpu-$MAJOR.js $
 jazzchord-$MAJOR.js  mdnn-$MAJOR.js MIDI-$MAJOR.js $
 nns-$MAJOR.js page-$MAJOR.js pedline-$MAJOR.js perc-$MAJOR.js $
 nns-$MAJOR.js page-$MAJOR.js pedline-$MAJOR.js perc-$MAJOR.js psvg-$MAJOR.js $
 roman-$MAJOR.js soloffs-$MAJOR.js sth-$MAJOR.js strtab-$MAJOR.js $
 temper-$MAJOR.js edit-$MAJOR.css edit-$MAJOR.js edit-$MAJOR.xhtml $
 abckbd-$MAJOR.js abckbd2-$MAJOR.js follow-$MAJOR.js
 temper-$MAJOR.js tropt-$MAJOR.js edit-$MAJOR.css edit-$MAJOR.js edit-$MAJOR.xhtml $
 abckbd-$MAJOR.js abckbd2-$MAJOR.js
# not mei2svg-$MAJOR.js

default all

# npm/fossil new release
# fossil new release
rule version
  command = npm version $out;$
	tag=`ag -Q '"version"' package.json|cut -d'"' -f4`;$
	fossil commit -m "New release v$$tag" -tag v$$tag;$
	$tool -v;$
	echo 'do "npm publish --otp <authy_code>"'
  command = v=`fossil changes|wc -l`;$
	if [ $$v -ne 0 ]; then $
		echo "!! There are changes !!";$
		exit 1;$
	fi;$
	v=`fossil describe`;$
	if [ "$$out"="patch" ]; then $
		v=`echo $$v|awk -F. -v OFS=. '{$$3 += 1; print}'`;$
	else $
		v=`echo $$v|awk -F. -v OFS=. '{$$2 += 1; $$3=0; print}'`;$
	fi;$
	fossil commit --allow-empty -m "New release $$v" -tag $$v;$
	n=`tail -1 abc2svg-1.js|wc -c`;$
	truncate -s -$$n abc2svg-1.js;$
	d=`date -Idate`;$
	echo "abc2svg.version=\"$$v\";abc2svg.vdate=\"$$d\"" >> abc2svg-1.js;$
	r="abc2svg-$$v";$
	ln -s . $$r;$
	sed "s#^#$$r/#" dist_bin | pax -wj -x tar -f abc2svg.tar.bz2;$
	rm -f abc2svg.zip;$
	sed "s#^#$$r/#" dist_bin | zip -r abc2svg.zip -@;$
	rm $$r;$
	echo "Don't forget to upload abc2svg.tar.bz2 and abc2svg.zip"
build minor: version
build patch: version

# temporary
rule nul
  command = touch $out
build follow-$MAJOR.js: nul
Changes to core/abc2svg.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - abc2svg.js
//
// Copyright (C) 2014-2021 Jean-Francois Moine
// Copyright (C) 2014-2022 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
25
26
27
28
29
30
31

32
33
34
35
36
37
38
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39







+







abc2svg.C = {
	BLEN: 1536,

	// symbol types
	BAR: 0,
	CLEF: 1,
	CUSTOS: 2,
	SM: 3,		// sequence marker (transient)
	GRACE: 4,
	KEY: 5,
	METER: 6,
	MREST: 7,
	NOTE: 8,
	PART: 9,
	REST: 10,
︙
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74







-
+







	SL_ALI_MSK: 0x70,	// align
		SL_ALIGN: 0x10,
		SL_CENTER: 0x20,
		SL_CLOSE: 0x40
    };

// !! tied to the symbol types in abc2svg.js !!
abc2svg.sym_name = ['bar', 'clef', 'custos', '', 'grace',
abc2svg.sym_name = ['bar', 'clef', 'custos', 'smark', 'grace',
		'key', 'meter', 'Zrest', 'note', 'part',
		'rest', 'yspace', 'staves', 'Break', 'tempo',
		'', 'block', 'remark']

	// key table - index = number of accidentals + 7
abc2svg.keys = [
	new Int8Array([-1,-1,-1,-1,-1,-1,-1 ]),	// 7 flat signs
︙
96
97
98
99
100
101
102
103
104
105
106
107
108






109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

125
126
127
128
129
130
131
97
98
99
100
101
102
103






104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

125
126
127
128
129
130
131
132







-
-
-
-
-
-
+
+
+
+
+
+















-
+







//		       C		 D
		[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
//	      E		     F		       G
	2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
//	      A			B
	5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6 ])
abc2svg.b40_a = new Int8Array(			// base-40 to accidental
//		         C		     D
		[-2, -1, 0, 1, 2, 0, -2, -1, 0, 1, 2, 0,
//		E		 F		     G
	-2, -1, 0, 1, 2, -2, -1, 0, 1, 2, 0, -2, -1, 0, 1, 2, 0,
//		A		    B
	-2, -1, 0, 1, 2, 0, -2, -1, 0, 1, 2 ])
//		         C		      D
		[-2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3,
//		E		 F		      G
	-2, -1, 0, 1, 2, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3,
//		A		     B
	-2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2 ])
abc2svg.b40_m = new Int8Array(			// base-40 to midi
//			 C		   D
		[-2, -1, 0, 1, 2, 0, 0, 1, 2, 3, 4, 0,
//	      E		     F		       G
	2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 0, 5, 6, 7, 8, 9, 0,
//	      A			    B
	7, 8, 9, 10, 11, 0, 9, 10, 11, 12, 13 ])
abc2svg.b40k =  new Int8Array(		// base-40 interval to possible transposition
//		        C		  D
		[36, 1, 2, 3, 8, 2, 2, 7, 8,13,14, 2,
//		    37
//	       E	      F		        G
	 8,13,14,19,20,13,14,19,20,25, 2,19,24,25,30,31, 2,
//					    20
//	       A		 B
	25,30,31,36,37, 2,31,36,37,38, 3 ])
	25,30,31,36,37, 2,31,36,37, 2, 3 ])
//				    2
abc2svg.b40sf = new Int8Array(		// base-40 interval to key signature
//		        C		   D
		[-2,-7, 0, 7, 2, 88, 0,-5, 2,-3, 4, 88,
//	       E	      F		        G
	 2,-3, 4,-1, 6,-3, 4,-1, 6, 1,88,-1,-6, 1,-4, 3,88,
//	       A		 B
︙
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
175
176
177
178
179
180
181

















182
183
184
185
186
187
188







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







	"aug7": "+7",
	"7+5": "+7",
	"7#5": "+7",
	"sus": "sus4",
	"7sus": "7sus4"
} // ch_alias

// extract one of the chord symbols
// With chords as "xxx(yyy)" or "[yyy];xxx"
// (!sel - default) returns "xxx" and (sel) returns "yyy"
abc2svg.cs_sel0 = /[\[(].*[\])]/g
abc2svg.cs_sel1 = /.*[\[(]|[\])].*/g
abc2svg.cs_filter = function(a_cs, sel) {
    var	i, cs,
	tcs = ""

	for (i = 0; i < a_cs.length; i++) {
		cs = a_cs[i]
		if (cs.type == 'g')
			tcs += cs.text
	}
	return tcs.replace(sel ? abc2svg.cs_sel1 : abc2svg.cs_sel0, '')
} // cs_filter()

// font weight
// reference:
//	https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
abc2svg.ft_w = {
	thin: 100,
	extralight: 200,
	light: 300,
︙
Changes to core/deco.js.
︙
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
71
72
73
74
75
76
77

78
79
80
81
82
83
84







-







	gmark: "3 grm 6 5 5",
	wedge: "0 wedge 8 3 3",	// (staccatissimo or spiccato)
	longphrase: "3 lphr 0 1 1",
	mediumphrase: "3 mphr 0 1 1",
	shortphrase: "3 sphr 0 1 1",
	turnx: "3 turnx 5,5 6 6",
	invertedturn: "3 turn 5,5 6 6",
	invertedturnx: "3 turnx 5,5 6 6",
	"0": "3 fng 5,5 3 3 0",
	"1": "3 fng 5,5 3 3 1",
	"2": "3 fng 5,5 3 3 2",
	"3": "3 fng 5,5 3 3 3",
	"4": "3 fng 5,5 3 3 4",
	"5": "3 fng 5,5 3 3 5",
	plus: "3 dplus 7 3 3",
︙
112
113
114
115
116
117
118
119

120
121

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138




139
140
141
142
143
144
145
111
112
113
114
115
116
117

118
119

120
121
122
123
124
125
126
127
128
129
130
131
132
133




134
135
136
137
138
139
140
141
142
143
144







-
+

-
+













-
-
-
-
+
+
+
+







	dacoda: "5 dacs 16 20 20 Da Coda",
	"D.C.": "5 dcap 16 10 10",
	"D.S.": "5 dsgn 16 10 10",
	"D.C.alcoda": "5 dacs 16 38 38 D.C. al Coda",
	"D.S.alcoda": "5 dacs 16 38 38 D.S. al Coda",
	"D.C.alfine": "5 dacs 16 38 38 D.C. al Fine",
	"D.S.alfine": "5 dacs 16 38 38 D.S. al Fine",
	fermata: "5 hld 12 7 7",
	fermata: "3 hld 12 7 7",
	fine: "5 dacs 16 10 10 Fine",
	invertedfermata: "7 hld 12 7 7",
	invertedfermata: "4 hld 12 7 7",
	segno: "5 sgno 22 8 8",
	f: "6 f 18 4 4",
	ff: "6 ff 18 6 6",
	fff: "6 fff 18 9 9",
	ffff: "6 ffff 18 12 12",
	mf: "6 mf 18 9 9",
	mp: "6 mp 18 10 10",
	p: "6 p 18 5 5",
	pp: "6 pp 18 9 9",
	ppp: "6 ppp 18 13 13",
	pppp: "6 pppp 18 17 17",
	pralltriller: "3 umrd 4,4 5 5",
	sfz: "6 sfz 18 8 8",
	ped: "4 ped 18 6 10",
	"ped-up": "4 pedoff 12 4 4",
	"ped(": "4 lped 20 5 5",
	"ped)": "4 lped 20 5 5",
	ped: "7 ped 18 6 10",
	"ped-up": "7 pedoff 12 4 4",
	"ped(": "7 lped 20 5 5",
	"ped)": "7 lped 20 5 5",
	"crescendo(": "6 cresc 18 0 0",
	"crescendo)": "6 cresc 18 0 0",
	"<(": "6 cresc 18 0 0",
	"<)": "6 cresc 18 0 0",
	"diminuendo(": "6 dim 18 0 0",
	"diminuendo)": "6 dim 18 0 0",
	">(": "6 dim 18 0 0",
︙
262
263
264
265
266
267
268
269
270
271




272
273
274
275
276
277
278
261
262
263
264
265
266
267



268
269
270
271
272
273
274
275
276
277
278







-
-
-
+
+
+
+







function up3(s, pos) {
	switch (pos & 0x07) {
	case C.SL_ABOVE:
		return 1	// true
	case C.SL_BELOW:
		return 0	// false
	}
	if (s.multi)
		return s.multi > 0
	return 1		// true
//	if (s.multi)
//		return s.multi > 0
//	return 1		// true
	return !s.second
} // up3()

// - of the dynamic and volume marks
function up6(s, pos) {
	switch (pos & 0x07) {
	case C.SL_ABOVE:
		return true
︙
435
436
437
438
439
440
441
442

443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460


461
462
463
464
465
466
467









468
469
470
471
472
473
474
435
436
437
438
439
440
441

442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458


459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483







-
+
















-
-
+
+







+
+
+
+
+
+
+
+
+







	else if (dd.ty == '_')
		up = 0
	else if (s.multi)
		up = s.multi > 0
	else
		up = s.stem < 0
	y = up ? s.ymx : s.ymn
	if (y > -6 && y < 30) {
	if (y > 0 && y < 24) {
		y = (((y + 9) / 6) | 0) * 6 - 6	// between lines
	}
	if (up) {
		y += dd.hd
		s.ymx = y + dd.h
	} else if (dd.name[0] == 'w') {		// wedge (no descent)
		de.inv = true
		y -= dd.h
		s.ymn = y
	} else {
		y -= dd.h
		s.ymn = y - dd.hd
	}
	de.y = y
	if (s.type == C.NOTE)
		de.x += s.notes[s.stem >= 0 ? 0 : s.nhd].shhd
	if (dd.name[0] == 'd'			/* if dot decoration */
	 && s.nflags >= -1) {			/* on stem */
	if (dd.name[0] == 'd') {		// if dot (staccato)
	    if (!(s.beam_st && s.beam_end)) {	// if in a beam sequence
		if (up) {
			if (s.stem > 0)
				de.x += 3.5	// stem_xoff
		} else {
			if (s.stem < 0)
				de.x -= 3.5
		}
	    } else {
		if (up && s.stem > 0) {
			y = s.y + (y - s.y) * .6
			if (y >= 27) {
				de.y = y	// put the dot a bit lower
				s.ymx = de.y + dd.h
			}
		}
	    }
	}
}

/* 6: dynamic marks */
function d_pf(de) {
	var	dd2, x2, x, up,
		s = de.s,
︙
505
506
507
508
509
510
511
512

513
514
515
516
517
518
519
514
515
516
517
518
519
520

521
522
523
524
525
526
527
528







-
+







				if (x2 > x)
					x = x2
			}
		}
	}

	de.x = x;
	de.y = y_get(s.st, up, x, de.val)
	de.y = y_get(s.st, up, x - dd.wl, de.val)
	if (!up)
		de.y -= dd.h
	else
		de.y += dd.hd
	/* (y_set is done later in draw_deco_staff) */
}

︙
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
730
731
732
733
734
735
736

737
738
739
740

741
742
743
744
745
746
747







-




-







			 || dd.glyph == "accent"
			 || dd.glyph == "roll")
				de.inv = 1
			s.ymn = y - dd.hd
		}
	}

	if (dd.func != 6) {			// if not d_pf
		if (y >= stafft)
			y_set(s.st, 1, x - dd.wl, w, y + dd.h)
		else if (y <= staffb)
			y_set(s.st, 0, x - dd.wl, w, y - dd.hd)
	}

	de.x = x;
	de.y = y
}

// add a decoration
/* syntax:
︙
897
898
899
900
901
902
903
904

905
906
907
908



909
910
911
912
913
914
915
904
905
906
907
908
909
910

911




912
913
914
915
916
917
918
919
920
921







-
+
-
-
-
-
+
+
+







	if (dd)
		return dd
	if ("<>^_@".indexOf(nm[0]) >= 0	// if position
	 && !/^([>^]|[<>]\d?[()])$/.test(nm)) {
		ty = nm[0]
		if (ty == '@') {
			p = nm.match(/@([-\d]+),([-\d]+)/)
			if (!p) {
			if (p)
				error(1, s, "Bad position in !$1!", nm)
				return
			}
			ty = p[0]
				ty = p[0]
			else
				ty = ''		// accept decorations starting with '@'
		}
		dd = deco_def(nm, nm.replace(ty, ''))
	} else {
		dd = deco_def(nm)
	}
	if (!dd)
		return
︙
1469
1470
1471
1472
1473
1474
1475
1476

1477
1478
1479
1480
1481
1482
1483
1475
1476
1477
1478
1479
1480
1481

1482
1483
1484
1485
1486
1487
1488
1489







-
+







				ix: a_de.length,
				defl: {},
				x: x,
				y: y,
				pos: pos
//				dy: 0
			}
			if (dd.name.indexOf("invert") == 0)
			if (dd.name.indexOf("inverted") >= 0)
				de.inv = 1
			if (s.type == C.BAR && !dd.ty)
				de.x -= s.wl / 2 - 2
			a_de.push(de)
			if (dd.dd_en) {
				de.ldst = true
			} else if (dd.dd_st) {
︙
1549
1550
1551
1552
1553
1554
1555
1556

1557
1558
1559
1560
1561
1562
1563
1555
1556
1557
1558
1559
1560
1561

1562
1563
1564
1565
1566
1567
1568
1569







-
+







					create_dh(s, m)
			}
		}
	} // create_all()

	// link the long decorations
	function ll_deco() {
		var	i, j, de, de2, dd, dd2, v, s, st,
	    var	i, j, de, de2, de3, dd, dd2, v, s, st,
			n_de = a_de.length

		// add ending decorations
		for (i = 0; i < n_de; i++) {
			de = a_de[i]
			if (!de.ldst)	// not the start of long decoration
				continue
︙
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610







1611
1612
1613






1614
1615
1616
1617
1618
1619
1620
1605
1606
1607
1608
1609
1610
1611





1612
1613
1614
1615
1616
1617
1618



1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631







-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+







				if (de.m != undefined)
					de2.m = de.m;
				a_de.push(de2)
			}
			de2.start = de;
			de2.defl.nost = de.defl.nost

			// handle same numbered decoration at a same time
			j = dd.name.slice(-1)
			if (j >= '0' && j <= '9'
			 && i > 0
			 && a_de[i - 1].s.time == de.s.time
			// handle same decoration ending at a same time
			j = i
			while (--j >= 0) {
				de3 = a_de[j]
				if (!de3.start)
					continue
				if (de3.s.time < s.time)
			 && a_de[i - 1].dd.name.slice(0, -2) ==
					dd.name.slice(0, -2))
				de2.prev = a_de[i - 1]
					break
				if (de3.dd.name == de2.dd.name) {
					de2.prev = de3
					break
				}
			}
		}

		// add starting decorations
		for (i = 0; i < n_de; i++) {
			de2 = a_de[i]
			if (!de2.lden	// not the end of long decoration
			 || de2.start)	// start already found
︙
1699
1700
1701
1702
1703
1704
1705
1706

1707
1708
1709
1710
1711
1712
1713
1710
1711
1712
1713
1714
1715
1716

1717
1718
1719
1720
1721
1722
1723
1724







-
+







	nd = a_de.length

	/* draw the repeat brackets */
	function draw_repbra(p_voice) {
		var s, s1, x, y, y2, i, p, w, wh, first_repeat;

		// search the max y offset of the line
		y = staff_tb[p_voice.st].topbar + 25	// 20 (vert bar) + 5 (room)
		y = staff_tb[p_voice.st].topbar + 15	// 10 (vert bar) + 5 (room)
		for (s = p_voice.sym; s; s = s.next) {
			if (s.type != C.BAR)
				continue
			if (!s.rbstart || s.norepbra)
				continue
/*fixme: line cut on repeat!*/
			if (!s.next)
︙
1726
1727
1728
1729
1730
1731
1732
1733

1734
1735
1736
1737
1738

1739
1740
1741
1742
1743
1744
1745
1737
1738
1739
1740
1741
1742
1743

1744
1745
1746
1747
1748

1749
1750
1751
1752
1753
1754
1755
1756







-
+




-
+







			}
			y2 = y_get(p_voice.st, true, s1.x, s.x - s1.x) + 2
			if (y < y2)
				y = y2

			// have room for the vertical lines and the repeat numbers
			if (s1.rbstart == 2) {
				y2 = y_get(p_voice.st, true, s1.x, 3) + 20
				y2 = y_get(p_voice.st, true, s1.x, 3) + 10
				if (y < y2)
					y = y2
			}
			if (s.rbstop == 2) {
				y2 = y_get(p_voice.st, true, s.x - 3, 3) + 20
				y2 = y_get(p_voice.st, true, s.x - 3, 3) + 10
				if (y < y2)
					y = y2
			}
			if (s1.text) {
				wh = strwh(s1.text);
				y2 = y_get(p_voice.st, true, s1.x + 4, wh[0]) +
						wh[1]
︙
1788
1789
1790
1791
1792
1793
1794
1795

1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807

1808
1809
1810
1811

1812
1813
1814

1815
1816
1817
1818
1819

1820
1821
1822
1823
1824
1825
1826
1799
1800
1801
1802
1803
1804
1805

1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817

1818
1819
1820
1821

1822
1823
1824

1825
1826
1827
1828
1829

1830
1831
1832
1833
1834
1835
1836
1837







-
+











-
+



-
+


-
+




-
+







					w = 12
				else if (s.bar_type[0] != ':')
//				      || s.bar_type == "]")
					w = 0		/* explicit repeat end */
				else
					w = 8
			} else {
				w = s.rbstop ? 0 : 8
				w = (s.rbstop && !s.rbstart) ? 0 : 8
			}
			w = (s.x - x - w)	// / staff_tb[p_voice.st].staffscale;

			if (!s.next		// 2nd ending at end of line
			 && !s.rbstop
			 && !p_voice.bar_start) { // continue on next line
				p_voice.bar_start = _bar(s)
				p_voice.bar_start.bar_type = ""
				p_voice.bar_start.rbstart = 1
			}
			if (s1.text)
				xy_str(x + 4, y2 - gene.curfont.size - 3,
				xy_str(x + 4, y2 - gene.curfont.size,
					s1.text);
			xypath(x, y2);
			if (s1.rbstart == 2)
				output += 'm0 20v-20';
				output += 'm0 10v-10';
			output+= 'h' + w.toFixed(1)
			if (s.rbstop == 2)
				output += 'v20';
				output += 'v10';
			output += '"/>\n';
			y_set(s1.st, true, x, w, y + 2)

			if (s.rbstart)
				s = s.prev
				s = s.prev.prev
		}
	} // draw_repbra()

	/* create the decorations tied to the staves */
	for (i = 0; i <= nstaff; i++)
		minmax[i] = {
			ymin: 0,
︙
1877
1878
1879
1880
1881
1882
1883
1884

1885
1886
1887
1888
1889
1890
1891
1888
1889
1890
1891
1892
1893
1894

1895
1896
1897
1898
1899
1900
1901
1902







-
+







	}

	draw_all_chsy()		// draw all chord symbols

	/* draw the repeat brackets */
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (p_voice.second || !p_voice.sym)
		if (p_voice.second || !p_voice.sym || p_voice.ignore)
			continue
		draw_repbra(p_voice)
	}
}

/* -- draw the measure bar numbers -- */
/* (scaled delayed output) */
︙
1915
1916
1917
1918
1919
1920
1921
1922

1923
1924
1925
1926
1927
1928
1929
1926
1927
1928
1929
1930
1931
1932

1933
1934
1935
1936
1937
1938
1939
1940







-
+







	bar_num = gene.nbar
	if (bar_num > 1) {
		if (cfmt.measurenb == 0) {
			any_nb = true;
			y = y_get(st, true, 0, 20)
			if (y < staff_tb[st].topbar + 14)
				y = staff_tb[st].topbar + 14;
			xy_str(0, y, bar_num.toString());
			xy_str(0, y - gene.curfont.size * .2, bar_num.toString())
			y_set(st, true, 0, 20, y + gene.curfont.size + 2)
		} else if (bar_num % cfmt.measurenb == 0) {
			for ( ; ; s = s.ts_next) {
				switch (s.type) {
				case C.CLEF:
				case C.KEY:
				case C.METER:
︙
1937
1938
1939
1940
1941
1942
1943
1944
1945


1946
1947
1948
1949


1950
1951
1952
1953
1954
1955
1956
1948
1949
1950
1951
1952
1953
1954


1955
1956
1957
1958


1959
1960
1961
1962
1963
1964
1965
1966
1967







-
-
+
+


-
-
+
+







		     if (s.type != C.BAR || !s.bar_num) {
			any_nb = true;
			w = w0
			if (bar_num >= 10)
				w *= bar_num >= 100 ? 3 : 2
			if (gene.curfont.pad)
				w += gene.curfont.pad * 2
			x = s.x - s.wl + 2	// if clef, shift a bit
			y = y_get(st, true, x, w)
			x = s.x - s.wl - w
			y = y_get(st, true, x, w) + 5
			if (y < staff_tb[st].topbar + 6)
				y = staff_tb[st].topbar + 6;
			y += 2 + gene.curfont.pad
			xy_str(x, y, bar_num.toString())
			y += gene.curfont.pad
			xy_str(x, y - gene.curfont.size * .2, bar_num.toString())
			y += gene.curfont.size + gene.curfont.pad
			y_set(st, true, x, w, y);
//			s.ymx = y
		     }
		}
	}

︙
1995
1996
1997
1998
1999
2000
2001
2002

2003
2004
2005
2006
2007
2008
2009
2006
2007
2008
2009
2010
2011
2012

2013
2014
2015
2016
2017
2018
2019
2020







-
+







					y = s.next.ys - gene.curfont.size
			} else {
				if (y < s.next.y)
					y = s.next.y
			}
		}
		y += 2 + gene.curfont.pad
		xy_str(x, y, bar_num.toString())
		xy_str(x, y - gene.curfont.size * .2, bar_num.toString())
		y += gene.curfont.size + gene.curfont.pad
		y_set(st, true, x, w, y);
//		s.ymx = y
	}
	gene.nbar = bar_num

	if (font_size)
︙
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2072
2073
2074
2075
2076
2077
2078


2079
2080
2081
2082
2083
2084
2085







-
-







				s.ymn = (dosh & 1) ? h : y;
				s.ymx = s.ymn + 14;
				anno_start(s)
			}
			writempo(s, s.x - 16, (dosh & 1) ? h : y);
			anno_stop(s);
			dosh >>= 1
			while (s.ts_next && s.ts_next.type == C.TEMPO)
				s = s.ts_next
		}
	}

	/* then, put the parts */
/*fixme: should reduce vertical space if parts don't overlap tempo...*/
	ymin = staff_tb[st].topbar + 6
	for (s = tsfirst; s; s = s.ts_next) {
︙
2106
2107
2108
2109
2110
2111
2112
2113

2114
2115
2116
2117
2118
2115
2116
2117
2118
2119
2120
2121

2122
2123
2124
2125
2126
2127







-
+





				s2.v = s.v
				s2.st = s2.wl = 0
				s2.wr = strwh(s2.text)[0]
				s2.ymn = -ht
				s2.ymx = s2.ymn + h
				anno_start(s2)
			}
			xy_str(s2.x, 2 - ht, s2.text)
			xy_str(s2.x, 2 - ht + gene.curfont.size * .22, s2.text)
			anno_stop(s2)
		}
	}
	return dy /= staff_tb[0].staffscale
}
Changes to core/draw.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - draw.js - draw functions
//
// Copyright (C) 2014-2022 Jean-Francois Moine
// Copyright (C) 2014-2023 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
25
26
27
28
29
30
31
32

33
34
35
36
37
38
39
25
26
27
28
29
30
31

32
33
34
35
36
37
38
39







-
+







	STEM_CH_MIN	= 14,	/* min stem height for chords under beams */
	STEM_CH_MIN2	= 10,	/* ... for notes with two beams */
	STEM_CH_MIN3	= 9,	/* ... for notes with three beams */
	STEM_CH_MIN4	= 9,	/* ... for notes with four beams */
	BEAM_DEPTH	= 3.2,	/* width of a beam stroke */
	BEAM_OFFSET	= .25,	/* pos of flat beam relative to staff line */
	BEAM_SHIFT	= 5,	/* shift of second and third beams */
	BEAM_STUB	= 8,	/* length of stub for flag under beam */ 
	BEAM_STUB	= 7,	/* length of stub for flag under beam */ 
	SLUR_SLOPE	= .5,	/* max slope of a slur */
	GSTEM		= 15,	/* grace note stem length */
	GSTEM_XOFF	= 2.3	/* x offset for grace note stem */

    var cache,
	anno_a = []		// symbols with annotations

︙
241
242
243
244
245
246
247


248

249
250
251
252
253
254
255
241
242
243
244
245
246
247
248
249

250
251
252
253
254
255
256
257







+
+
-
+







	/* have room for the symbols in the staff */
	max_stem_err = 0;		/* check stem lengths */
	s = s1
	if (two_dir) {				/* 2 directions */
/*fixme: more to do*/
		ys = ((s1.grace ? 3.5 : BEAM_SHIFT) * (nflags - 1) +
			BEAM_DEPTH) * .5
		if (s1.nflags == s2.nflags)
			;
		if (s1.stem != s2.stem && s1.nflags < s2.nflags)
		else if (s1.stem != s2.stem && s1.nflags < s2.nflags)
			b += ys * s2.stem
		else
			b += ys * s1.stem
	} else if (!s1.grace) {		/* normal notes */
		var beam_h = BEAM_DEPTH + BEAM_SHIFT * (nflags - 1)
//--fixme: added for abc2svg
		while (s.ts_prev
︙
339
340
341
342
343
344
345


346
347
348
349
350
351
352
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356







+
+








	/* have room for the gracenotes, bars and clefs */
/*fixme: test*/
    if (!two_staves && !two_dir)
	for (s = s1.next; ; s = s.next) {
		switch (s.type) {
		case C.REST:		/* cannot move rests in multi-voices */
			if (!s.multi)
				break
			g = s.ts_next
			if (!g || g.st != st
			 || (g.type != C.NOTE && g.type != C.REST))
				break
//fixme:too much vertical shift if some space above the note
//fixme:this does not fix rest under beam in second voice (ts_prev)
			/*fall thru*/
︙
452
453
454
455
456
457
458
459

460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477





478
479



480
481
482
483
484
485
486

487
488
489
490
491
492
493
456
457
458
459
460
461
462

463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486


487
488
489
490
491
492
493
494
495

496
497
498
499
500
501
502
503







-
+


















+
+
+
+
+
-
-
+
+
+






-
+







	bm.nflags = nflags
	return true
}

/* -- draw the beams for one word -- */
/* (the staves are defined) */
function draw_beams(bm) {
	var	s, i, beam_dir, shift, bshift, bstub, bh, da,
	var	s, i, beam_dir, shift, bshift, bstub, bh, da, bd,
		k, k1, k2, x1,
		s1 = bm.s1,
		s2 = bm.s2

	/* -- draw a single beam -- */
	function draw_beam(x1, x2, dy, h, bm,
				 n) {		/* beam number (1..n) */
		var	y1, dy2,
			s = bm.s1,
			nflags = s.nflags

		if (s.ntrem)
			nflags -= s.ntrem
		if (s.trem2 && n > nflags) {
			if (s.dur >= C.BLEN / 2) {
				x1 = s.x + 6;
				x2 = bm.s2.x - 6
			} else if (s.dur < C.BLEN / 4) {
			    var	dx = x2 - x1
				if (dx < 16) {
					x1 += dx / 4
					x2 -= dx / 4
				} else {
				x1 += 5;
				x2 -= 6
					x1 += 5
					x2 -= 6
				}
			}
		}

		y1 = bm.a * x1 + bm.b - dy;
		x2 -= x1;
		x2 /= stv_g.scale;
		dy2 = bm.a * x2
		dy2 = bm.a * x2 * stv_g.scale
		xypath(x1, y1, true);
		output += 'l' + x2.toFixed(1) + ' ' + (-dy2).toFixed(1) +
			'v' + h.toFixed(1) +
			'l' + (-x2).toFixed(1) + ' ' + dy2.toFixed(1) +
			'z"/>\n'
	} // draw_beam()

︙
501
502
503
504
505
506
507

508
509
510
511
512
513
514
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525







+







		bh = BEAM_DEPTH
	} else {
		bshift = 3.5;
		bstub = 3.2;
		shift = .29;
		bh = 1.8
	}
	bh /= stv_g.scale

/*fixme: quick hack for stubs at end of beam and different stem directions*/
	beam_dir = s1.stem
	if (s1.stem != s2.stem
	 && s1.nflags < s2.nflags)
		beam_dir = s2.stem
	if (beam_dir < 0)
︙
580
581
582
583
584
585
586

587
588
589
590
591
592
593
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605







+







					break
				s = k
			}
			k2 = s
			while (k2.type != C.NOTE)
				k2 = k2.prev;
			x1 = k1.xs
			bd = beam_dir
			if (k1 == k2) {
				if (k1 == s1) {
					x1 += bstub
				} else if (k1 == s2) {
					x1 -= bstub
				} else if (k1.beam_br1
				        || (k1.beam_br2
︙
608
609
610
611
612
613
614




615


616
617

618
619
620
621
622
623
624
620
621
622
623
624
625
626
627
628
629
630

631
632
633

634
635
636
637
638
639
640
641







+
+
+
+
-
+
+

-
+







						 || (k1.nflags == k.nflags
						  && k1.dots < k.dots))
							x1 += bstub
						else
							x1 -= bstub
					}
				}
				if (k1.stem != beam_dir) {
					bd = k1.stem
					k1.ys = bm.a * k1.xs + bm.b
						- staff_tb[k1.st].y - bh
			}
				}
			}
			draw_beam(x1, k2.xs,
				  shift * beam_dir,
				  shift * bd,
				  bh, bm, i)
			if (s == s2)
				break
		}
	}
	if (s1.tmp)
		unlksym(s1)
︙
862
863
864
865
866
867
868
869


870










































871
872
873
874
875
876
877
878



879

880
881
882
883
884
885
886
879
880
881
882
883
884
885

886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941

942
943
944
945
946
947
948
949







-
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








+
+
+
-
+







	flat2 = new Int8Array([-12, 9, -12, 9, -12, 9])

Abc.prototype.draw_keysig = function(x, s) {
	var	old_sf = s.k_old_sf,
		st = s.st,
		staffb = staff_tb[st].y,
		i, shift, p_seq,
		clef_ix = s.k_y_clef
		clef_ix = s.k_y_clef,
	a_acc = s.k_a_acc			// accidental list [pit, acc]

	// set the accidentals when K: with modified accidentals
	function set_k_acc(a_acc, sf) {
	    var i, j, n, nacc, p_acc,
		accs = [],
		pits = []

		if (sf > 0) {
			for (nacc = 0; nacc < sf; nacc++) {
				accs[nacc] = 1			// sharp
				pits[nacc] = [26, 23, 27, 24, 21, 25, 22][nacc]
			}
		} else {
			for (nacc = 0; nacc < -sf; nacc++) {
				accs[nacc] = -1			// flat
				pits[nacc] = [22, 25, 21, 24, 20, 23, 26][nacc]
			}
		}
		n = a_acc.length
		for (i = 0; i < n; i++) {
			p_acc = a_acc[i]
			for (j = 0; j < nacc; j++) {
				if (pits[j] == p_acc.pit) {
					accs[j] = p_acc.acc
					break
				}
			}
			if (j == nacc) {
				accs[j] = p_acc.acc
				pits[j] = p_acc.pit
				nacc++
			}
		}
		for (i = 0; i < nacc; i++) {
			p_acc = a_acc[i]
			if (!p_acc)
				p_acc = a_acc[i] = {}
			p_acc.acc = accs[i]
			p_acc.pit = pits[i]
		}
	} // set_k_acc()

	// ---- draw_keysig ---
	if (clef_ix & 1)
		clef_ix += 7;
	clef_ix /= 2
	while (clef_ix < 0)
		clef_ix += 7;
	clef_ix %= 7

	/* normal accidentals */
	if (a_acc && !s.exp)			// if added accidentals
		set_k_acc(a_acc, s.k_sf)	// merge them into the key

	if (!s.k_a_acc) {
	if (!a_acc) {

		/* put neutrals if 'accidental cancel' */
		if (s.fmt.cancelkey || s.k_sf == 0) {

			/* when flats to sharps, or sharps to flats, */
			if (s.k_sf == 0
			 || old_sf * s.k_sf < 0) {
︙
920
921
922
923
924
925
926




927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947

948
949
950
951

952
953
954
955
956
957
958
959
960


961
962
963
964
965
966
967
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013

1014
1015
1016
1017

1018
1019
1020
1021
1022
1023
1024
1025


1026
1027
1028
1029
1030
1031
1032
1033
1034







+
+
+
+




















-
+



-
+







-
-
+
+







				x += 2
				for (; i < old_sf; i++) {
					xygl(x, staffb + shift, "acc3");
					shift += p_seq[i];
					x += 5.5
				}
			}
			if (s.k_bagpipe == 'p') {	// K:Hp - add the g natural
				xygl(x, staffb + 27, "acc3")
				x += 5.5
			}
		}

		/* new flats */
		if (s.k_sf < 0) {
			shift = flat_cl[clef_ix];
			p_seq = shift < 18 ? flat1 : flat2
			for (i = 0; i > s.k_sf; i--) {
				xygl(x, staffb + shift, "acc-1");
				shift += p_seq[-i];
				x += 5.5
			}
			if (s.fmt.cancelkey && i > old_sf) {
				x += 2
				for (; i > old_sf; i--) {
					xygl(x, staffb + shift, "acc3");
					shift += p_seq[-i];
					x += 5.5
				}
			}
		}
	} else if (s.k_a_acc.length) {
	} else if (a_acc.length) {

		/* explicit accidentals */
		var	acc,
			last_acc = s.k_a_acc[0].acc,
			last_acc = a_acc[0].acc,
			last_shift = 100,
			s2 = {
				st: st,
				nhd: 0,
				notes: [{}]
			}

		for (i = 0; i < s.k_a_acc.length; i++) {
			acc = s.k_a_acc[i];
		for (i = 0; i < a_acc.length; i++) {
			acc = a_acc[i];
			shift = (s.k_y_clef	// clef shift
				+ acc.pit - 18) * 3
			while (shift < -3)		// let the accidentals inside the staff
				shift += 21
			while (shift > 27)
				shift -= 21
			if (i != 0
︙
1092
1093
1094
1095
1096
1097
1098
1099

1100
1101
1102
1103
1104
1105
1106
1107
1108








1109

1110
1111
1112
1113
1114
1115

1116
1117
1118
1119
1120
1121
1122
1159
1160
1161
1162
1163
1164
1165

1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183

1184
1185
1186
1187
1188
1189

1190
1191
1192
1193
1194
1195
1196
1197







-
+









+
+
+
+
+
+
+
+
-
+





-
+







	set_color();
	anno_a.push(s)
}

// -- draw a multi-measure rest --
// (the staves are defined)
function draw_mrest(s) {
    var	x1, x2,
    var	x1, x2, prev,
	p_st = staff_tb[s.st],
	y = p_st.y + (p_st.topbar + p_st.botbar) / 2,
	p = s.nmes.toString()

	if (!s.next) {
		error(1, s, "Lack of bar after multi-measure rest")
		return
	}
	set_scale(s)

	prev = s		// search the start of the previous time sequence
	while (!prev.seqst)
		prev = prev.ts_prev
	prev = prev.ts_prev
	while (!prev.seqst)
		prev = prev.ts_prev

	x1 = s.prev.x + 20
	x1 = prev.x + 20
	x2 = s.next.x - 20
	s.x = (x1 + x2) / 2
	anno_start(s)
	out_XYAB('<path d="mX Y', x1 + .6, y - 2.7)
	output += 'v2.7h-1.4v-10.8h1.4v2.7h'
		+ (x2 - x1 - 2.8).toFixed(1)
		+ ((x2 - x1 - 2.8) / stv_g.scale).toFixed(1)
		+ 'v-2.7h1.4v10.8h-1.4v-2.7z"/>\n'
	out_XYAB('<text x ="X" y="Y" text-anchor="middle">A</text>\n',
		s.x, y + 22, m_gl(p))
	anno_a.push(s)
} // draw_mrest()

function grace_slur(s) {
︙
1669
1670
1671
1672
1673
1674
1675
1676
1677

1678
1679
1680
1681
1682
1683
1684
1685

1686
1687
1688
1689
1690
1691
1692
1744
1745
1746
1747
1748
1749
1750


1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767







-
-
+








+







}

// draw a slur between two chords / notes
/* (the staves are not yet defined) */
/* (delayed output) */
/* (not a pretty routine, this) */
function draw_slur(path,	// list of symbols under the slur
		not1,		// note if start on a note head
		sl) {		// ending variables: type, note, end on a note
		sl) {		// slur variables: type, end symbol, note
    var	i,
	k, g, x1, y1, x2, y2, height, addy,
	a, y, z, h, dx, dy,
	ty = sl.ty,
	dir = (ty & 0x07) == C.SL_ABOVE ? 1 : -1,
	n = path.length,
	i1 = 0,
	i2 = n - 1,
	not1 = sl.nts,		// if the slur start on a note
	k1 = path[0],
	k2 = path[i2]

/*fixme: if two staves, may have upper or lower slur*/

	var	nn = 1,
		upstaff = k1.st,
︙
1762
1763
1764
1765
1766
1767
1768
1769
1770


1771
1772
1773
1774
1775
1776
1777
1837
1838
1839
1840
1841
1842
1843


1844
1845
1846
1847
1848
1849
1850
1851
1852







-
-
+
+







				} else {
					y1 = k1.y - 8
				}
			}
		}
	}

	if (sl.is_note) {				// end on a note
		y2 = 3 * (sl.note.pit - 18) + 2 * dir
	if (sl.nte) {					// slur ending on a note
		y2 = 3 * (sl.nte.pit - 18) + 2 * dir
		x2 -= 3
	} else {					// end on a chord
		y2 = dir > 0 ? k2.ymx + 2 : k2.ymn - 2
		if (k2.type == C.NOTE) {
			if (dir > 0) {
				if (k2.stem > 0) {
					x2 += 1
︙
1984
1985
1986
1987
1988
1989
1990
1991

1992
1993
1994
1995
1996
1997
1998
2059
2060
2061
2062
2063
2064
2065

2066
2067
2068
2069
2070
2071
2072
2073







-
+







		y = a * k.x + addy
		if (k.ymx < y)
			k.ymx = y
		else if (k.ymn > y)
			k.ymn = y
		if (i == i2 - 1) {
			dx = x2
			if (k2.sl1)
			if (sl.nte)
				dx -= 5;
			if (i)
				y -= height / 3
		} else {
			dx = path[i + 1].x
		}
		if (i != 0)
︙
2006
2007
2008
2009
2010
2011
2012
2013

2014
2015

2016
2017

2018
2019
2020
2021
2022







































2023
2024

2025










2026
2027
2028
2029
2030
2031
2032
2033

2034
2035
2036
2037
2038
2039
2040
2081
2082
2083
2084
2085
2086
2087

2088


2089
2090

2091

2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135

2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152



2153
2154
2155
2156
2157
2158
2159
2160







-
+
-
-
+

-
+
-




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+

+
+
+
+
+
+
+
+
+
+





-
-
-
+








/* -- draw the slurs between 2 symbols --*/
function draw_slurs(s, last) {
    var	gr1, i, m, note, sls, nsls

	// draw a slur knowing the start and stop elements
	function draw_sls(s,		// start symbol
			sl,		// stop note
			sl) {		// slur variables
			snote) {	// optional start note
	    var	k, v, i, dir,
	    var	k, v, i, dir, s3,
		path = [],
		enote = sl.note,
		s2 = sl.se			// slur end
		s2 = enote.s			// end of slur

		if (last && s2.time > last.time)
			return			// will be drawn next time

		// handle slurs without start or end
		switch (sl.loc) {
		case 'i':			// no start
			s3 = s.ts_prev
			for (s = s3; s; s = s.ts_prev) {
				if (s.dur) {
					if (s.v == s2.v) {
						s3 = s
						break
					}
					if (s.st == s2.st) {
						s3 = s
						continue
					} else if (!s3) {
						s3 = s
					}
				} else if (!s3) {
					s3 = s
				}
			}
			s = s3
			break
		case 'o':			// no end
			for (s3 = s; s3.ts_next; s3 = s3.ts_next)
				;
			s2 = s3
			for (; s3; s3 = s3.ts_prev) {
				if (s3.v == s.v) {
					s2 = s3
					break
				}
				if (s3.st == s.st)
					s2 = s3
				if (s3.ts_prev.time != s2.time)
					break
			}
			break
		}

		// if the slur continues on the next music line,
		// stop it on the invisible bar at end of current line
		// stop it at the end of the current line
		if (tsnext && s2.time >= tsnext.time) {
		    if (s2.grace && s2.time == tsnext.time) {
			for (s3 = tsnext; s3 && s3.time == s2.time; s3 = s3.ts_next) {
				if (s3.type == C.GRACE)
//fixme: check if s2 in the grace notes
					break
			}
			if (s3.type == C.GRACE)
				s3 = null
		    }
		    if (!s2.grace || s2.time == tsnext.time || !s3) {
			s.p_v.sls.push(sl);		// continuation on next line
			s2 = s.p_v.s_next.prev		// one voice
			while (s2.next)
				s2 = s2.next;		// search the ending bar
			sl = Object.create(sl);		// new slur
			sl.note = {
				s: s2
			}
		    }
		}

		// set the slur position
		switch (sl.ty & 0x07) {
		case C.SL_ABOVE: dir = 1; break
		case C.SL_BELOW: dir = -1; break
		default:
︙
2073
2074
2075
2076
2077
2078
2079
2080


2081
2082
2083
2084
2085
2086
2087
2193
2194
2195
2196
2197
2198
2199

2200
2201
2202
2203
2204
2205
2206
2207
2208







-
+
+







				s = s.next
			else
				s = s.ts_next
		}

		if (!s2.grace) {		// if end on a normal note
			while (s) {
				if (s.v == v)
				if (s.v == v
				 && !s.invis)
					path.push(s)
				if (s == s2)
					break
				s = s.ts_next
			}
		} else if (s.grace) {		// if start/end in the same sequence
			while (1) {
︙
2110
2111
2112
2113
2114
2115
2116
2117

2118
2119
2120
2121
2122

2123
2124
2125
2126
2127
2128
2129
2231
2232
2233
2234
2235
2236
2237

2238
2239
2240
2241
2242

2243
2244
2245
2246
2247
2248
2249
2250







-
+




-
+







				s = s.next
			}
		}

		// if some nested slurs/tuplets, draw them
		for (i = 1; i < path.length - 1; i++) {
			s = path[i]
			if (s.sls || s.sl1)
			if (s.sls)
				draw_slurs(s, last)
			if (s.tp)
				draw_tuplet(s)
		}
		draw_slur(path, snote, sl)
		draw_slur(path, sl)
		return 1			// slur drawn, remove it
	} // draw_sls()

	// code of draw_slurs()
	while (1) {
		if (!s || s == last) {
			if (!gr1		// if end of grace notes
︙
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178

2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189


2190
2191

2192
2193
2194
2195
2196
2197
2198
2199
2200
2201

2202
2203
2204
2205
2206
2207
2208
2265
2266
2267
2268
2269
2270
2271
















2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282

2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297

2298
2299
2300
2301
2302
2303
2304
2305
2306
2307

2308
2309
2310
2311
2312
2313
2314
2315







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-











-
+











+
+

-
+









-
+







			for (i = 0; i < sls.length; i++) {
				if (!draw_sls(s, sls[i]))
					nsls.push(sls[i])
			}
			if (nsls.length)
				s.sls = nsls
		}
		if (s.sl1) {			// slurs from the note heads
			for (m = 0; m <= s.nhd; m++) {
				note = s.notes[m]
				if (note.sls) {
					sls = note.sls
					note.sls = null
					nsls = []
					for (i = 0; i < sls.length; i++) {
						if (!draw_sls(s, sls[i], note))
							nsls.push(sls[i])
					}
					if (nsls.length)
						note.sls = nsls
				}
			}
		}
		s = s.next
	}
}

/* -- draw a tuplet -- */
/* (the staves are not yet defined) */
/* (delayed output) */
/* See http://moinejf.free.fr/abcm2ps-doc/tuplets.html
 * for the value of 'tp.f' */
function draw_tuplet(s1) {
    var	s2, s3, g, upstaff, nb_only,
	x1, x2, y1, y2, xm, ym, a, s0, yy, yx, dy, a, dir, r, w,
	x1, x2, y1, y2, xm, ym, a, s0, yy, yx, dy, a, dir, r,
	tp = s1.tp.shift()		// tuplet parameters

	if (!s1.tp.length)
		delete s1.tp		// last tuplet

	// treat the slurs and the nested tuplets
	upstaff = s1.st
	set_dscale(s1.st)
	for (s2 = s1; s2; s2 = s2.next) {
		switch (s2.type) {
		case C.GRACE:
			if (!s2.sl1)
				continue
			for (g = s2.extra; g; g = g.next) {
				if (g.sls || g.sl1)
				if (g.sls)
					draw_slurs(g)
			}
			// fall thru
		default:
			continue
		case C.NOTE:
		case C.REST:
			break
		}
		if (s2.sls || s2.sl1)
		if (s2.sls)
			draw_slurs(s2)
		if (s2.st < upstaff)
			upstaff = s2.st
		if (s2.tp)
			draw_tuplet(s2)
		if (s2.tpe)
			break
︙
2218
2219
2220
2221
2222
2223
2224
2225

2226
2227

2228
2229
2230
2231
2232
2233
2234
2235

2236
2237
2238
2239
2240
2241
2242
2325
2326
2327
2328
2329
2330
2331

2332
2333

2334
2335
2336
2337
2338
2339
2340
2341

2342
2343
2344
2345
2346
2347
2348
2349







-
+

-
+







-
+







		error(1, s1, "No end of tuplet in this music line")
		return
	}

	dir = tp.f[3]				// 'where'
	if (!dir) {				// if auto
		s3 = s1
		while (s3.type != C.NOTE)
		while (s3 && !s3.stem)		// (may have tuplets of rests!)
			s3 = s3.next
		dir = s3.stem > 0 ? C.SL_ABOVE : C.SL_BELOW
		dir = (s3 && s3.stem < 0) ? C.SL_BELOW : C.SL_ABOVE
	}

	if (s1 == s2				// tuplet with 1 note (!)
	 || tp.f[1] == 2) {			// what == nothing
		nb_only = true
	} else if (tp.f[1] == 1) {			/* 'what' == slur */
		nb_only = true;
		draw_slur([s1, s2], null, {ty: dir})
		draw_slur([s1, s2], {ty: dir})
	} else {

		/* search if a bracket is needed */
		if (tp.f[0] != 2		// if 'when' != always
		 && s1.type == C.NOTE && s2.type == C.NOTE) {
			nb_only = true
			for (s3 = s1; ; s3 = s3.next) {
︙
2396
2397
2398
2399
2400
2401
2402

2403
2404
2405
2406
2407
2408
2409
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517







+







			a = s0
	} else {
		if (a > 0)
			a = 0
		else if (a < s0)
			a = s0
	}
	a = s1.fmt.beamslope * a / (s1.fmt.beamslope + Math.abs(a))
	if (a * a < .1 * .1)
		a = 0

	/* shift up bracket if needed */
	dy = 0
	for (s3 = s1; ; s3 = s3.next) {
		if (!s3.dur			/* not a note or a rest */
︙
2427
2428
2429
2430
2431
2432
2433
2434
2435

2436
2437
2438
2439
2440
2441
2442
2535
2536
2537
2538
2539
2540
2541


2542
2543
2544
2545
2546
2547
2548
2549







-
-
+







	/* shift the slurs / decorations */
	ym += 6
	for (s3 = s1; ; s3 = s3.next) {
		if (s3.st == upstaff) {
			yy = ym + (s3.x - xm) * a
			if (s3.ymx < yy)
				s3.ymx = yy
			w = s3.next.x - s3.x - 10
			y_set(upstaff, true, s3.x - w, w * 2, yy)
			y_set(upstaff, true, s3.x - 3, 6, yy)
		}
		if (s3 == s2)
			break
	}

    // below
    } else {	/* lower voice of the staff: the bracket is below the staff */
︙
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515

2516
2517
2518
2519
2520
2521
2522
2613
2614
2615
2616
2617
2618
2619



2620
2621
2622
2623
2624
2625
2626
2627







-
-
-
+







	/* shift the slurs / decorations */
	ym -= 2
	for (s3 = s1; ; s3 = s3.next) {
		if (s3.st == upstaff) {
			yy = ym + (s3.x - xm) * a
			if (s3.ymn > yy)
				s3.ymn = yy;
			w = s3.next.x - s3.x - 10
			if (s3.next)
				y_set(upstaff, false, s3.x - w, w * 2, yy)
			y_set(upstaff, false, s3.x - 3, 6, yy)
		}
		if (s3 == s2)
			break
	}
    } /* lower voice */

	if (tp.f[2] == 1) {			/* if 'which' == none */
︙
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719

2720
2721
2722
2723
2724
2725
2726
2805
2806
2807
2808
2809
2810
2811

2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822

2823
2824
2825
2826
2827
2828
2829
2830







-











-
+








/* -- draw the symbols near the notes -- */
/* (the staves are not yet defined) */
/* order:
 * - scaled
 *   - beams
 *   - decorations near the notes
 *   - measure bar numbers
 *   - decorations tied to the notes
 *   - tuplets and slurs
 * - not scaled
 *   - measure numbers
 *   - lyrics
 *   - staff decorations
 *   - chord symbols
 *   - repeat brackets
 * The buffer output is delayed until the definition of the staff system
 */
function draw_sym_near() {
    var	p_voice, p_st, s, v, st, y, g, w, i, st, dx, top, bot, ymn, slur,
    var	p_voice, p_st, s, v, st, y, g, w, i, st, dx, top, bot, ymn,
	output_sav = output;

	// set the staff offsets of a beam
	function set_yab(s1, s2) {
	    var y,
		k = realwidth / YSTEP,
		i = (s1.x / k) | 0,
︙
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788

2789
2790
2791
2792
2793
2794
2795
2872
2873
2874
2875
2876
2877
2878

2879
2880
2881
2882
2883
2884


2885
2886
2887
2888

2889
2890
2891
2892
2893
2894
2895
2896







-






-
-




-
+







		var	bm = {},
			first_note = true;

		p_voice = voice_tb[v]
		for (s = p_voice.sym; s; s = s.next) {
			switch (s.type) {
			case C.GRACE:
				slur = 0
				for (g = s.extra; g; g = g.next) {
					if (g.beam_st && !g.beam_end) {
						self.calculate_beam(bm, g)
						if (bm.s2)
							set_yab(g, bm.s2)
					}
					if (g.sls || g.sl1)
						slur++
				}
				if (!s.p_v.ckey.k_bagpipe	// no slur when bagpipe
				 && s.fmt.graceslurs
				 && !s.gr_shift			// tied to previous note
				 && !slur			// explicit slur
				 && !s.sl1			// explicit slur
				 && !s.ti1			// some tie
				 && s.next
				 && s.next.type == C.NOTE)
					grace_slur(s)
				break
			}
		}
︙
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878




2879
2880
2881
2882
2883


2884
2885
2886

2887
2888
2889

2890
2891
2892
2893
2894
2895
2896
2969
2970
2971
2972
2973
2974
2975




2976
2977
2978
2979
2980
2981



2982
2983



2984



2985
2986
2987
2988
2989
2990
2991
2992







-
-
-
-
+
+
+
+


-
-
-
+
+
-
-
-
+
-
-
-
+







			}
			dx += s.notes[0].shhd;
			y_set(s.st, false, s.x + dx, w, s.ymn)
		}

		/* have room for the accidentals */
		if (s.notes[s.nhd].acc) {
			y = s.y + 8
			if (s.ymx < y)
				s.ymx = y;
			y_set(s.st, true, s.x, 0, y)
			y = 3 * (s.notes[s.nhd].pit - 18)
				+ (s.notes[s.nhd].acc == -1	// flat
					? 11 : 10)
			y_set(s.st, true, s.x - 10, 10, y)
		}
		if (s.notes[0].acc) {
			y = s.y
			if (s.notes[0].acc == 1		// sharp
			 || s.notes[0].acc == 3)	// natural
			y = 3 * (s.notes[0].pit - 18)
				- (s.notes[0].acc == -1		// flat
				y -= 7
			else
				y -= 5
					? 5 : 10)
			if (s.ymn > y)
				s.ymn = y;
			y_set(s.st, false, s.x, 0, y)
			y_set(s.st, false, s.x - 10, 10, y)
		}
	}

	draw_deco_note()

	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v];
︙
2948
2949
2950
2951
2952
2953
2954
2955

2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968

2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987

2988
2989
2990
2991

2992
2993

2994
2995
2996
2997
2998
2999
3000
3044
3045
3046
3047
3048
3049
3050

3051
3052
3053
3054
3055
3056
3057
3058
3059
3060

3061
3062

3063
3064
3065
3066
3067















3068
3069



3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080







-
+









-


-
+




-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+

-
-
-
+


+








	set_dscale(-1);
	output = output_sav
}

/* -- draw the name/subname of the voices -- */
function draw_vname(indent, stl) {
    var	p_voice, n, st, v, a_p, p, y, name_type, h, h2,
    var	p_voice, n, st, v, a_p, p, y, h, h2,
	staff_d = []

	for (st = stl.length; st >= 0; st--) {
		if (stl[st])
			break
	}
	if (st < 0)
		return

	// check if full or sub names
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (!p_voice.sym || !cur_sy.voices[v])
		if (!cur_sy.voices[v])
			continue
		st = cur_sy.voices[v].st
		if (!stl[st])
			continue
		if (p_voice.new_name) {
			name_type = 2
			break
		}
		if (p_voice.snm)
			name_type = 1
	}
	if (!name_type)
		return
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (!p_voice.sym || !cur_sy.voices[v])
			continue
		st = cur_sy.voices[v].st
		if (!stl[st])
		if (!gene.vnt)
			continue
		if (p_voice.new_name)
			delete p_voice.new_name;
		p = name_type == 2 ? p_voice.nm : p_voice.snm
		p = gene.vnt == 2 ? p_voice.nm : p_voice.snm
		if (!p)
			continue
		delete p_voice.new_name
		if (!staff_d[st])
			staff_d[st] = p
		else
			staff_d[st] += "\n" + p
	}
	if (!staff_d.length)
		return
︙
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080

3081




3082
3083
3084
3085
3086
3087
3088
3138
3139
3140
3141
3142
3143
3144





3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156

3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167







-
-
-
-
-











+
-
+
+
+
+







				y = val
		}
//	}

	/* draw the parts and tempo indications if any */
	y += draw_partempo(st, y)

	if (!gene.st_print[st]) {
		p_staff.y = -y
		return y
	}

	/* set the vertical offset of the 1st staff */
	y *= p_staff.staffscale;
	staffsep = fmt.staffsep * .5 +
			p_staff.topbar * p_staff.staffscale
	if (y < staffsep)
		y = staffsep
	if (y < p_staff.ann_top)	// absolute annotation
		y = p_staff.ann_top;
	p_staff.y = -y;

	/* set the offset of the other staves */
	for (prev_staff = 0; prev_staff < st; prev_staff++)
	prev_staff = st
		staff_tb[prev_staff].y = -y
	if (!gene.st_print[st])		// no staff
		return y

	var sy_staff_prev = sy.staves[prev_staff]
	for (st++; st <= nstaff; st++) {
		if (!gene.st_print[st])
			continue
		p_staff = staff_tb[st]
		staffsep = sy_staff_prev.sep || cfmt.sysstaffsep;
		maxsep = sy_staff_prev.maxsep || cfmt.maxsysstaffsep;
︙
3110
3111
3112
3113
3114
3115
3116

3117

3118
3119
3120
3121
3122
3123
3124
3189
3190
3191
3192
3193
3194
3195
3196

3197
3198
3199
3200
3201
3202
3203
3204







+
-
+







			dy = staffsep;
		maxsep += p_staff.topbar * p_staff.staffscale
		if (dy > maxsep)
			dy = maxsep;
		y += dy;
		p_staff.y = -y;

		while (!gene.st_print[++prev_staff])
		prev_staff = st;
			staff_tb[prev_staff].y = -y
		while (1) {
			sy_staff_prev = sy.staves[prev_staff]
			if (sy_staff_prev)
				break
			sy = sy.next
		}
	}
︙
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3243
3244
3245
3246
3247
3248
3249

3250
3251
3252
3253
3254
3255
3256







-







	// return the height of the whole staff system
	return y + dy
}

/* -- draw the staff systems and the measure bars -- */
function draw_systems(indent) {
	var	s, s2, st, x, x2, res, sy,
		staves_bar, bar_force,
		xstaff = [],
		stl = [],		// all staves in the line
		bar_bot = [],
		bar_height = [],
		ba = [],		// bars [symbol, bottom, height]
		sb = "",
		thb = ""
︙
3196
3197
3198
3199
3200
3201
3202
3203

3204
3205
3206
3207


3208
3209
3210
3211
3212

3213
3214
3215
3216
3217

3218
3219
3220
3221

3222
3223
3224
3225

3226
3227
3228
3229
3230
3231

3232
3233

3234
3235
3236
3237
3238
3239
3240
3241
3242

3243
3244
3245
3246
3247
3248
3249
3250
3251
3252

3253
3254
3255
3256
3257
3258
3259
3275
3276
3277
3278
3279
3280
3281

3282
3283
3284
3285

3286
3287
3288
3289
3290
3291

3292

3293
3294
3295

3296
3297
3298
3299

3300
3301
3302
3303

3304
3305
3306
3307
3308
3309

3310
3311

3312
3313
3314
3315
3316
3317
3318
3319
3320

3321
3322
3323
3324
3325
3326
3327
3328
3329
3330

3331
3332
3333
3334
3335
3336
3337
3338







-
+



-
+
+




-
+
-



-
+



-
+



-
+





-
+

-
+








-
+









-
+







			dy = (cur_sy.staves[st].flags & STOP_BAR) ?
					0 : bar_bot[st]
		}
	} // bar_set()

	/* -- draw a staff -- */
	function draw_staff(st, x1, x2) {
		var	w, ws, i, dy, ty,
	    var	w, i, dy, ty,
			y = 0,
			ln = "",
			stafflines = staff_tb[st].stafflines,
			l = stafflines.length
			l = stafflines.length,
			il = 6 * staff_tb[st].staffscale // interline

		if (!/[\[|]/.test(stafflines))
			return				// no line
		w = x2 - x1;
		set_sscale(st);
		set_sscale(-1)
		ws = w / stv_g.scale

		// check if default staff
		if (cache && cache.st_l == stafflines
		 && cache.st_ws == (ws | 0)) {
		 && cache.st_w == (w | 0)) {
			xygl(x1, staff_tb[st].y, 'stdef' + cfmt.fullsvg)
			return
		}
		for (i = 0; i < l; i++, y -= 6) {
		for (i = 0; i < l; i++, y -= il) {
			if (stafflines[i] == '.')
				continue
			dy = 0
			for (; i < l; i++, y -= 6, dy -= 6) {
			for (; i < l; i++, y -= il, dy -= il) {
				switch (stafflines[i]) {
				case '.':
				case '-':
					continue
				case ty:
					ln += 'm-' + ws.toFixed(1) +
					ln += 'm-' + w.toFixed(1) +
						' ' + dy +
						'h' + ws.toFixed(1);
						'h' + w.toFixed(1);
					dy = 0
					continue
				}
				if (ty != undefined)
					ln += '"/>\n';
				ty = stafflines[i]
				ln += '<path class="' +
					(ty == '[' ? 'slthW' : 'slW') +
					'" d="m0 ' + y + 'h' + ws.toFixed(1);
					'" d="m0 ' + y + 'h' + w.toFixed(1);
				dy = 0
			}
			ln += '"/>'
		}
		y = staff_tb[st].y
		if (!cache
		 && w > get_lwidth() - 10) {
			cache = {
				st_l: stafflines,
				st_ws: ws | 0
				st_w: w | 0
			}
			i = 'stdef' + cfmt.fullsvg;
			if (ln.indexOf('<path', 1) < 0)
				glyphs[i] = ln.replace('path', 'path id="' + i + '"')
			else
				glyphs[i] = '<g id="' + i + '">\n' + ln + '\n</g>';
			xygl(x1, y, i)
︙
3425
3426
3427
3428
3429
3430
3431
3432
3433


3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470



3471
3472




3473
3474
3475
3476
3477

3478
3479
3480
3481
3482


3483
3484
3485

3486
3487
3488
3489
3490

3491
3492
3493
3494
3495
3496
3497
3504
3505
3506
3507
3508
3509
3510


3511
3512
3513
3514
3515
3516








3517
3518


















3519
3520
3521
3522
3523
3524
3525
3526


3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539


3540
3541

3542

3543
3544
3545
3546


3547
3548
3549
3550
3551
3552
3553
3554







-
-
+
+




-
-
-
-
-
-
-
-


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-





+
+
+
-
-
+
+
+
+





+



-
-
+
+
-

-
+



-
-
+







		}
	} // hl_rest()

	// ---- draw_systems() ----

	/* draw the staff, skipping the staff breaks */
	for (st = 0; st <= nstaff; st++) {
		xstaff[st] = !cur_sy.st_print[st] ? -1 : 0;
		stl[st] = cur_sy.st_print[st]	// staff in the line
		stl[st] = gene.st_print[st] || cur_sy.st_print[st] // staff in the line
		xstaff[st] = !stl[st] ? -1 : 0;
	}
	bar_set();
	draw_lstaff(0)
	for (s = tsfirst; s; s = s.ts_next) {
		if (bar_force && s.time != bar_force) {
			bar_force = 0
			for (st = 0; st <= nstaff; st++) {
				if (!cur_sy.st_print[st])
					xstaff[st] = -1
			}
			bar_set()
		}
		switch (s.type) {
		case C.STAVES:
			staves_bar = s.ts_prev.type == C.BAR ? s.ts_prev.x : 0
		    if (!staves_bar) {
			for (s2 = s.ts_next; s2; s2 = s2.ts_next) {
				if (s2.time != s.time)
					break
				switch (s2.type) {
				case C.BAR:
				case C.CLEF:
				case C.KEY:
				case C.METER:
					staves_bar = s2.x
					continue
				}
				break
			}
			if (!s2)
				staves_bar = realwidth;
		    }
			sy = s.sy
			for (st = 0; st <= nstaff; st++) {
				x = xstaff[st]
				if (x < 0) {		// no staff yet
					if (sy.st_print[st]) {
						if (s.ts_prev.bar_type)
							xstaff[st] = s.ts_prev.x
						else if (s.ts_next.bar_type)
						xstaff[st] = staves_bar ?
							staves_bar : (s.x - s.wl - 2)
							xstaff[st] = s.x
						else
							xstaff[st] = s.ts_prev.x
								+ s.ts_prev.wr
						stl[st] = true
					}
					continue
				}
				if (sy.st_print[st]	// if not staff stop
				 && cur_sy.staves[st]
				 && sy.staves[st].stafflines ==
						cur_sy.staves[st].stafflines)
					continue
				if (staves_bar) {
					x2 = staves_bar;
				if (s.ts_prev.bar_type) {
					x2 = s.ts_prev.x
					bar_force = s.time
				} else {
					x2 = s.x - s.wl - 2;
					x2 = (s.ts_prev.x + s.x) / 2
					xstaff[st] = -1
				}
				draw_staff(st, x, x2)
				if (sy.st_print[st])
					xstaff[st] = x2
				xstaff[st] = sy.st_print[st] ? x2 : -1
			}
			cur_sy = sy;
			bar_set()
			continue
		case C.BAR:		// display the bars after the staves
			if (s.invis || !s.bar_type
			 || !cur_sy.st_print[s.st])
︙
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3618
3619
3620
3621
3622
3623
3624


3625
3626
3627
3628
3629
3630
3631







-
-







//				s.invis = true
//			break
		}
	}

	// draw the end of the staves
	for (st = 0; st <= nstaff; st++) {
		if (bar_force && !cur_sy.st_print[st])
			continue
		x = xstaff[st]
		if (x < 0 || x >= realwidth)
			continue
		draw_staff(st, x, realwidth)
	}

	// the ledger lines
︙
3618
3619
3620
3621
3622
3623
3624
3625

3626
3627
3628
3629
3630
3631
3632
3673
3674
3675
3676
3677
3678
3679

3680
3681
3682
3683
3684
3685
3686
3687







-
+







				draw_note(s, !bm.s2);
				anno_a.push(s)
			}
			if (s == bm.s2)
				bm.s2 = null
			break
		case C.REST:
			if (!staff_tb[st].topbar)
			if (!gene.st_print[st])
				break
			draw_rest(s);
			break
		case C.BAR:
			break			/* drawn in draw_systems */
		case C.CLEF:
			if (s.time >= staff_tb[st].clef.time)
︙
Changes to core/format.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - format.js - formatting functions
//
// Copyright (C) 2014-2021 Jean-Francois Moine
// Copyright (C) 2014-2022 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
87
88
89
90
91
92
93

94
95
96
97
98

99
100
101
102
103
104
105
87
88
89
90
91
92
93
94
95
96
97
98

99
100
101
102
103
104
105
106







+




-
+







	musicspace: 6,
//	notespacingfactor: "1.3, 38",
	partsfont: {name: txt_ff, size: 15},
	parskipfac: .4,
	partsspace: 8,
//	pageheight: 29.7 * CM,
	pagewidth: 21 * CM,
	"propagate-accidentals": "o",		// octave
	printmargin: 0,
	rightmargin: 1.4 * CM,
	rbmax: 4,
	rbmin: 2,
	repeatfont: {name: txt_ff, size: 13},
	repeatfont: {name: txt_ff, size: 9},
	scale: 1,
	slurheight: 1.0,
	spatab: 	// spacing table (see "notespacingfactor" and set_space())
		new Float32Array([	// default = "1.3, 38"
			10.2, 13.3, 17.3, 22.48, 29.2,
			38,
			49.4, 64.2, 83.5, 108.5]),
︙
126
127
128
129
130
131
132
133


134
135
136
137
138
139
140
127
128
129
130
131
132
133

134
135
136
137
138
139
140
141
142







-
+
+







	tupletfont: {name: txt_ff, style: "italic", size: 12},
	vocalfont: {name: txt_ff, weight: "bold", size: 13},
	vocalspace: 10,
	voicefont: {name: txt_ff, weight: "bold", size: 13},
//	voicescale: 1,
	writefields: "CMOPQsTWw",
	wordsfont: {name: txt_ff, size: 16},
	wordsspace: 5
	wordsspace: 5,
	"writeout-accidentals": "n"
}

// parameters that are used in the symbols
var sfmt = {
bardef: true,
barsperstaff: true,
beamslope: true,
︙
181
182
183
184
185
186
187
188

189
190
191
192
193
194
195
183
184
185
186
187
188
189

190
191
192
193
194
195
196
197







-
+







		return
	}
	font_scale_tb[a[0]] = scale
}

// set the width factor of a font
function set_font_fac(font) {
    var scale = font_scale_tb[font.name]
    var scale = font_scale_tb[font.fname || font.name]

	if (!scale)
		scale = 1.1;
	font.swfac = font.size * scale
}

// %%xxxfont fontname|* [encoding] [size|*]
︙
210
211
212
213
214
215
216


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231

232
233
234
235
236
237
238
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234

235
236
237
238
239
240
241
242







+
+














-
+







		font = {
			pad: 0
		}
	} else {
		font = {
			name: font.name,
			size: font.size,
			weight: font.weight,
			style: font.style,
			box: font.box,
			pad: font.pad || 0
		}
	}
	cfmt[xxxfont] = font;

	// fill the values
	a = p.match(/\s+(no)?box(\s|$)/)
	if (a) {				// if box
		if (a[1]) {
			font.box = false	// nobox
			font.pad = 0
		} else {
			font.box = true
			font.pad = font.size * .4 - 3
			font.pad = 2.5
		}
		p = p.replace(a[0], a[2])
	}
	a = p.match(/\s+padding=([\d.]+)(\s|$)/)
	if (a) {				// if padding
		font.pad = a[1] ? +a[1] : 0
		p = p.replace(a[0], a[2])
︙
267
268
269
270
271
272
273


274
275
276
277
278
279
280
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286







+
+







	a = p.match(/\s+([0-9.]+|\*)$/)
	if (a) {
		if (a[1] != "*")
			font.size = +a[1]
		p = p.replace(a[0], "")
	}

	font.fname = p			// full name for scale factor

	// accept url(...) as the font name
	if (p[0] == 'u' && p.slice(0, 4) == "url(") {
		n = p.indexOf(')', 1)
		if (n < 0) {
			syntax(1, "No end of url in font family")
			return
		}
︙
296
297
298
299
300
301
302




303
304
305
306
307
308
309
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319







+
+
+
+







		}

		// extract the font attributes
		a = p.match(/[- ]?[nN]ormal/)
		if (a) {
			font.normal = true
			p = p.replace(a[0], '')
			if (font.weight)
				font.weight = null
			if (font.style)
				font.style = null
		}

		// font weight
		a = p.match(abc2svg.ft_re)
		if (a) {
			font.weight = abc2svg.ft_w[a[0].replace(/[ -]/, '')
					.toLowerCase()]
︙
335
336
337
338
339
340
341


342
343
344
345
346
347
348
349
350
351
352

353
354
355
356
357
358
359
345
346
347
348
349
350
351
352
353
354
355
356
357


358
359
360
361

362
363
364
365
366
367
368
369







+
+




-
-




-
+







		default:
//hack: the font "Figurato" is used for figured bass
			if (p.indexOf("Fig") > 0)
				font.figb = true
			break
		}
	}
	font.name = p

	if (font.size)
		set_font_fac(font)
	else
		font.swfac = 0

	font.name = p
}

// get a length with a unit - return the number of pixels
function get_unit(param) {
    var	v = param.toLowerCase().match(/([\d.]+)(.*)/)
    var	v = param.toLowerCase().match(/(-?[\d.]+)(.*)/)
	if (!v)
		return NaN

	v[1] = +v[1]
	switch (v[2]) {
	case "cm":
		return v[1] * CM
︙
445
446
447
448
449
450
451


452

453
454
455
456
457
458
459
455
456
457
458
459
460
461
462
463

464
465
466
467
468
469
470
471







+
+
-
+







		}
	}
}

// set a voice specific parameter
function set_v_param(k, v) {
	k = [k + '=', v]
	if (parse.state < 3)
		memo_kv_parm(curvoice ? curvoice.id : '*', k)
	if (curvoice)
	else if (curvoice)
		set_kv_parm(k)
	else
		memo_kv_parm('*', k)
}

function set_page() {
	if (!img.chg)
︙
508
509
510
511
512
513
514

515

516
517
518
519
520
521
522
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536







+

+







			break
		}
		cfmt[cmd] = v
		break
	case "abc-version":
	case "bgcolor":
	case "fgcolor":
	case "propagate-accidentals":
	case "titleformat":
	case "writeout-accidentals":
		cfmt[cmd] = param
		break
	case "beamslope":
	case "breaklimit":			// float values
	case "lineskipfac":
	case "maxshrink":
	case "pagescale":
︙
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
572
573
574
575
576
577
578

579
580
581
582
583
584
585







-







	case "flatbeams":
	case "graceslurs":
	case "graceword":
	case "hyphencont":
	case "keywarn":
	case "linewarn":
	case "quiet":
	case "singleline":
	case "squarebreve":
	case "splittune":
	case "straightflags":
	case "stretchstaff":
	case "timewarn":
	case "titlecaps":
	case "titleleft":
︙
671
672
673
674
675
676
677
678
679
680
681
682










683
684
685
686
687
688
689
684
685
686
687
688
689
690





691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707







-
-
-
-
-
+
+
+
+
+
+
+
+
+
+







			}
		if (parse.ufmt)
			cfmt[cmd] = new Float32Array(3)
		for (i = 0; i < 3; i++)
			cfmt[cmd][i] = +v[i]
		break
	case "tuplets":
		cfmt[cmd] = param.split(/\s+/);
		v = cfmt[cmd][3]
		if (v			// if 'where'
		 && (posval[v]))	// translate the keyword
			cfmt[cmd][3] = posval[v]
		v = param.split(/\s+/)
		f = v[3]
		if (f)			// if 'where'
			f = posval[f]	// translate the keyword
		if (f)
			v[3] = f
		if (curvoice)
			curvoice.tup = v
		else
			cfmt[cmd] = v
		break
	case "infoname":
		set_infoname(param)
		break
	case "notespacingfactor":
		v = param.match(/([.\d]+)[,\s]*(\d+)?/)
		if (v) {
︙
723
724
725
726
727
728
729




















730
731
732
733
734
735
736
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







		cfmt.sound = "play"		// without clef
		break
	case "pos":
		cmd = param.match(/(\w*)\s+(.*)/)
		if (!cmd || !cmd[2]) {
			syntax(1, "Error in %%pos")
			break
		}
		if (cmd[1].slice(0, 3) == 'tup'		// special case for %%pos tuplet
		 && curvoice) {				// inside tune
			if (!curvoice.tup)
				curvoice.tup = cfmt.tuplets
			else
				curvoice.tup = Object.create(curvoice.tup)
			v = posval[cmd[2]]
			switch (v) {
			case C.SL_ABOVE:
				curvoice.tup[3] = 1
				break
			case C.SL_BELOW:
				curvoice.tup[3] = 2
				break
			case C.SL_HIDDEN:
				curvoice.tup[2] = 1
				break
			}
			break
		}
		set_pos(cmd[1], cmd[2])
		break
	case "sounding-score":
		if (cfmt.sound != "play")
			cfmt.sound = "sounding"
		break
︙
752
753
754
755
756
757
758

759
760
761
762
763
764

765
766
767
768
769
770
771
790
791
792
793
794
795
796
797
798
799
800
801
802

803
804
805
806
807
808
809
810







+





-
+







		cfmt.rightmargin = v;
		img.chg = true
		break
	case "textoption":
		cfmt[cmd] = get_textopt(param)
		break
	case "dynalign":
	case "singleline":
	case "stretchlast":
	case "titletrim":
		v = +param
		if (isNaN(v))
			v = get_bool(param) ? 0 : 1
		if (cmd[0] == 's') {		// stretchlast
		if (cmd[1] == 't') {		// stretchlast
			if (v < 0 || v > 1) {
				syntax(1, errs.bad_val, '%%' + cmd)
				break
			}
		}
		cfmt[cmd] = v
		break
︙
847
848
849
850
851
852
853
854

855
856
857
858
859
860
861
862
863
864
865

866
867
868
869
870
871
872
886
887
888
889
890
891
892

893
894
895
896
897
898
899
900
901
902
903

904
905
906
907
908
909
910
911







-
+










-
+







		if (font.src)
			add_fstyle("@font-face{\n\
 font-family:" + font.name + ";\n\
 src:" + font.src + "}")
		if (font == cfmt.musicfont)	// add more music font style
			add_fstyle(".f" + font.fid
				+ (cfmt.fullsvg || "")
				+ ' text,tspan{fill:currentColor;white-space:pre}')
				+ ' text,tspan{white-space:pre}')
	}
}

// get the font of the 'xxxfont' parameter
function get_font(fn) {
    var	font, font2, fid, st

	fn += "font"
	font = cfmt[fn]
	if (!font) {
		syntax(1, "Unknown font $1", '$' + fn[1]);
		syntax(1, "Unknown font $1", fn)
		return gene.curfont
	}

	if (!font.name || !font.size) {		// if incomplete user font
		font2 = Object.create(gene.deffont)
		if (font.name)
			font2.name = font.name
︙
Changes to core/front.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - front.js - ABC parsing front-end
//
// Copyright (C) 2014-2021 Jean-Francois Moine
// Copyright (C) 2014-2022 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292

293
294
295
296
297
298
299
269
270
271
272
273
274
275






276
277
278
279
280
281
282
283
284
285

286
287
288
289
290
291
292
293







-
-
-
-
-
-










-
+







		if (flag && src.indexOf('\\') >= 0)
			return cnv_escape(src, flag)
		return src
	} // uncomment()

	function end_tune() {
		generate()
		set_page()		// the page layout may have changed
		if (info.W)
			put_words(info.W);
		put_history();
		parse.state = 0		// file header
		blk_flush()		// (force end of block)
		cfmt = sav.cfmt;
		info = sav.info;
		char_tb = sav.char_tb;
		glovar = sav.glovar;
		maps = sav.maps;
		mac = sav.mac;
		maci = sav.maci;
		parse.tune_v_opts = null;
		parse.scores = null;
		parse.ufmt = false
		delete parse.part
		delete parse.ctrl
		init_tune()
		img.chg = true;
		set_page();
	} // end_tune()

	// get %%voice
	function do_voice(select, in_tune) {
︙
422
423
424
425
426
427
428

429
430
431
432
433
434
435
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430







+







		eol++
		if (eol == bol) {		// empty line
			if (parse.state == 1) {
				parse.istart = bol;
				syntax(1, "Empty line in tune header - ignored")
			} else if (parse.state >= 2) {
				end_tune()
				parse.state = 0
				if (parse.select) {	// skip to next tune
					eol = file.indexOf('\nX:', parse.eol)
					if (eol < 0)
						eol = eof
					parse.eol = eol
				}
			}
︙
488
489
490
491
492
493
494

495
496
497
498
499
500
501
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497







+







					syntax(1, "No $1 after %%$2",
							end.slice(1), a[1]);
					parse.eol = eof
					continue
				}
				self.do_begin_end(b, uncomment(a[2]),
					file.slice(eol + 1, i)
						.replace(/\n%[^%].*$/gm,'')
						.replace(/^%%/gm,''))
				parse.eol = file.indexOf('\n', i + 6)
				if (parse.eol < 0)
					parse.eol = eof
				continue
			}
			switch (a[1]) {
︙
Changes to core/gchord.js.
︙
17
18
19
20
21
22
23
24

25
26
27
28
29
30
31
17
18
19
20
21
22
23

24
25
26
27
28
29
30
31







-
+







// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// -- parse a chord symbol / annotation --
// the result is added in the global variable a_gch
// 'type' may be a single '"' or a string '"xxx"' created by U:
function parse_gchord(type) {
    var	c, text, gch, x_abs, y_abs, type,
    var	c, text, gch, x_abs, y_abs,
	i, j, istart, iend,
		ann_font = get_font("annotation"),
		h_ann = ann_font.size,
		line = parse.line

	function get_float() {
		var txt = ''
︙
46
47
48
49
50
51
52
53


54
55
56
57
58
59
60
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60
61







-
+
+







		i = ++line.index		// search the ending double quote
		while (1) {
			j = line.buffer.indexOf('"', i)
			if (j < 0) {
				syntax(1, "No end of chord symbol/annotation")
				return
			}
			if (line.buffer[j - 1] != '\\')
			if (line.buffer[j - 1] != '\\'
			 || line.buffer[j - 2] == '\\')	// (string ending with \\")
				break
			i = j + 1
		}
		text = cnv_escape(line.buffer.slice(line.index, j))
		line.index = j
		iend = parse.bol + line.index + 1
	}
︙
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
84
85
86
87
88
89
90

91
92
93
94
95
96
97







-







				y_abs = 0
			} else {
				y_abs = get_float()
				if (c != ' ')
					i--
			}
			gch.x = x_abs;
			y_abs -= h_ann * .3	// center vertically
			gch.y = y_abs
			break
		case '^':
			gch.pos = C.SL_ABOVE
			// fall thru
		case '_':
			if (c == '_')
︙
126
127
128
129
130
131
132
133

134
135


136
137
138
139
140
141
142
126
127
128
129
130
131
132

133
134
135
136
137
138
139
140
141
142
143
144







-
+


+
+







		while (1) {
			c = text[i]
			if (!c)
				break
			switch (c) {
			case '\\':
				c = text[++i]
				if (!c || c == 'n')
				if (c == 'n')
					break
				gch.text += '\\'
				if (!c)
					break
			default:
				gch.text += c;
				i++
				continue
			case '&':			/* skip "&xxx;" */
				while (1) {
					gch.text += c;
︙
170
171
172
173
174
175
176
177

178

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197

198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219

220
221
222
223
224
225
226
227

228
229
230
231
232








233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
172
173
174
175
176
177
178

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

200
201
202
203
204
205
206
207
208
209











210
211
212
213
214
215
216
217
218
219

220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239



240
241
242
243
244
245
246







-
+

+


















-
+









-
-
-
-
-
-
-
-
-
-
-


+







-
+





+
+
+
+
+
+
+
+






-
-
-







	}
}

// transpose a chord symbol
var	note_names = "CDEFGAB",
	acc_name = ["bb", "b", "", "#", "##"]

	function gch_tr1(p, transp) {
	function gch_tr1(p) {
	    var	i, o, n, a, ip, b40,
		tr = curvoice.tr_sco,
		csa = p.split('/')

		for (i = 0; i < csa.length; i++) {	// main and optional bass
			p = csa[i];
			o = p.search(/[A-G]/)
			if (o < 0)
				continue		// strange chord symbol!
			ip = o + 1
			a = 0
			while (p[ip] == '#' || p[ip] == '\u266f') {
				a++;
				ip++
			}
			while (p[ip] == 'b' || p[ip] == '\u266d') {
				a--;
				ip++
			}
			n = note_names.indexOf(p[o]) + 16
			b40 = (abc2svg.pab40(n, a) + transp + 200) % 40
			b40 = (abc2svg.pab40(n, a) + tr + 200) % 40
			b40 = abc2svg.b40k[b40]
			csa[i] = p.slice(0, o) +
					note_names[abc2svg.b40_p[b40]] +
					acc_name[abc2svg.b40_a[b40] + 2] +
					p.slice(ip)
		}
		return csa.join('/')
	} // gch_tr1

function gch_transp(s) {
    var	gch,
	i = s.a_gch.length

	while (--i >= 0) {
		gch = s.a_gch[i]
		if (gch.type == 'g')
			gch.text = gch_tr1(gch.text, curvoice.vtransp)
	}
}

// parser: add the parsed list of chord symbols and annotations
//	to the symbol (note, rest or bar)
//	and transpose the chord symbols
function csan_add(s) {
    var	i, gch

	// there cannot be chord symbols on measure bars
	if (s.type == C.BAR) {
		for (i = 0; i < a_gch.length; i++) {
			if (a_gch[i].type == 'g') {
				syntax(1,
				error(1, s,
				       "There cannot be chord symbols on measure bars")
				a_gch.splice(i)
			}
		}
	}

	if (curvoice.tr_sco) {
		for (i = 0; i < a_gch.length; i++) {
			gch = a_gch[i]
			if (gch.type == 'g')
				gch.text = gch_tr1(gch.text)
		}
	}

	if (s.a_gch)
		s.a_gch = s.a_gch.concat(a_gch)
	else
		s.a_gch = a_gch
	a_gch = null

	if (curvoice.vtransp)
		gch_transp(s)
} // csan_add

// generator: build the chord symbols / annotations
// (possible hook)
Abc.prototype.gch_build = function(s) {

	/* split the chord symbols / annotations
︙
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
267
268
269
270
271
272
273

274
275
276
277
278
279
280
281

282
283
284
285
286
287
288







-








-







					return "&#x1d12b;"
				});
		} else {
			if (gch.type == '@'
			 && !user.anno_start && !user.anno_stop) {
				set_font(gch.font)
				gch.text = str2svg(gch.text)
				gch.text.wh = [0, 0]
				continue		/* no width */
			}
		}

		/* set the offsets and widths */
		set_font(gch.font);
		gch.text = str2svg(gch.text)
		wh = gch.text.wh
		wh[1] += gch.font.pad * 2
		switch (gch.type) {
		case '@':
			break
		case '^':			/* above */
		case '_':			/* below */
			xspc = wh[0] * GCHPRE
			if (xspc > 8)
︙
336
337
338
339
340
341
342
343

344
345
346
347

348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385

386
387
388

389
390
391
392
393
394
395
396
397
398

399
400

401
402
403
404

405
406
407
408
409








410
411
412
413
414
415
416
332
333
334
335
336
337
338

339
340
341
342

343
344
345
346
347
348

349
350

351

352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371




372
373

374
375


376
377


378
379
380
381
382
383

384
385

386
387
388
389

390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410







-
+



-
+





-


-

-




















-
-
-
-


-
+

-
-
+

-
-






-
+

-
+



-
+





+
+
+
+
+
+
+
+







	if (s.invis && s.play)	// play sequence: no chord nor annotation
		return
    var	y2,
	an = s.a_gch[i],
	h = an.text.wh[1],
	pad = an.font.pad,
	w = an.text.wh[0] + pad * 2,
	dy = 0
	dy = h * .22			// descent

	if (an.font.figb) {
		h *= 2.4
		dy = an.font.size * 1.3
		dy += an.font.size * 1.3
	}

	switch (an.type) {
	case '_':			// below
		y -= h + pad
		y_set(s.st, 0, x, w, y - pad)
		break
	case '^':			// above
		y = y_get(s.st, 1, x, w)
		y += pad
		y_set(s.st, 1, x, w, y + h + pad)
		break
	case '<':			// left
	case '>':			// right
		if (an.type == '<') {
/*fixme: what symbol space?*/
			if (s.notes[0].acc)
				x -= s.notes[0].shac
			x -= pad
		} else {
			if (s.xmx)
				x += s.xmx
			if (s.dots)
				x += 1.5 + 3.5 * s.dots
			x += pad
		}
		y += (s.type == C.NOTE ?
				(((s.notes[s.nhd].pit + s.notes[0].pit) >> 1) -
						18) * 3 :
				12)		// fixed offset on rests and bars
			- h / 2
		if (y > 24)
			y_set(s.st, 1, x, w, y + h + pad)
		if (y < 0)
			y_set(s.st, 0, x, w, y - pad)
		break
	default:			// chord symbol
		if (y >= 0) {
		if (y >= 0)
			y += pad
			y_set(s.st, 1, x, w, y + h + pad)
		} else {
		else
			y -= h + pad
			y_set(s.st, 0, x, w, y - pad)
		}
		break
	case '@':			// absolute
		y += (s.type == C.NOTE ?
				(((s.notes[s.nhd].pit + s.notes[0].pit) >> 1) -
						18) * 3 :
				12)		// fixed offset on rests and bars
			+ h * .2
			- h / 2
		if (y > 0) {
			y2 = y + h * .8
			y2 = y + h + pad + 2
			if (y2 > staff_tb[s.st].ann_top)
				staff_tb[s.st].ann_top = y2
		} else {
			y2 = y
			y2 = y - 2
			if (y2 < staff_tb[s.st].ann_bot)
				staff_tb[s.st].ann_bot = y2
		}
		break
	}

	if (an.type != '@') {
		if (y > 24)
			y_set(s.st, 1, x, w, y + h + pad + 2)
		else if (y < 0)
			y_set(s.st, 0, x, w, y - pad)
	}

	use_font(an.font)
	set_font(an.font)
	set_dscale(s.st)
	if (user.anno_start)
		user.anno_start("annot", an.istart, an.iend,
			x - 2, y + h + 2, w + 4, h + 4, s)
	xy_str(x, y + dy, an.text)
︙
438
439
440
441
442
443
444
445

446
447
448
449
450
451
452
432
433
434
435
436
437
438

439
440
441
442
443
444
445
446







-
+







			if (!an)
				continue
			if (an.pos == C.SL_ABOVE) {
				x = s.x + an.x
				w = an.text.wh[0]
				if (w && x + w > realwidth)
					x = realwidth - w // let the text in the page
				y = y_get(s.st, 1, x, w)	// y / staff
				y = y_get(s.st, 1, x, w) + 2	// y / staff
				if (an.type == 'g' && y < minmax[s.st].yup)
					y = minmax[s.st].yup
			} else if (an.pos == C.SL_BELOW
				|| an.pos == C.SL_HIDDEN) {
				continue
			} else {
				x = s.x + an.x
︙
467
468
469
470
471
472
473
474

475
476
477
478
479
480
481
461
462
463
464
465
466
467

468
469
470
471
472
473
474
475







-
+







			if (!an
			 || an.pos != C.SL_BELOW)
				continue
			x = s.x + an.x
			w = an.text.wh[0]
			if (w && x + w > realwidth)	// let the text inside the page
				x = realwidth - w
			y = y_get(s.st, 0, x, w)	// y / staff
			y = y_get(s.st, 0, x, w) - 2	// y / staff
			if (an.type == 'g' && y > minmax[s.st].ydn)
				y = minmax[s.st].ydn
			self.draw_gchord(i, s, x, y)
		}
	} // set_an_yl()

	// get the number of chord symbols / annotations
︙
495
496
497
498
499
500
501
502

503
504
505
506

507
508
509
510
511
512
513
489
490
491
492
493
494
495

496
497
498
499

500
501
502
503
504
505
506
507







-
+



-
+







		if (i > n_an)
			n_an = i
		while (--i >= 0) {
			if (an[i].type == 'g') {
				an = an[i]
				w = an.text.wh[0]
				if (an.pos == C.SL_ABOVE) {
					y = y_get(s.st, true, s.x, w)
					y = y_get(s.st, true, s.x, w) + 2
					if (y > minmax[s.st].yup)
						minmax[s.st].yup = y
				} else if (an.pos == C.SL_BELOW) {
					y = y_get(s.st, false, s.x, w)
					y = y_get(s.st, false, s.x, w) - 2
					if (y < minmax[s.st].ydn)
						minmax[s.st].ydn = y
				}
				break
			}
		}
	}
︙
Changes to core/license.
1
2
3

1
2

3


-
+
// abc2svg - ABC to SVG translator
// @source: https://chiselapp.com/user/moinejf/repository/abc2svg
// Copyright (C) 2014-2021 Jean-Francois Moine - LGPL3+
// Copyright (C) 2014-2023 Jean-Francois Moine - LGPL3+
Changes to core/lyrics.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - lyrics.js - lyrics
//
// Copyright (C) 2014-2021 Jean-Francois Moine
// Copyright (C) 2014-2022 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
200
201
202
203
204
205
206
207

208
209
210
211
212
213
214
200
201
202
203
204
205
206

207
208
209
210
211
212
213
214







-
+







				case '*':
				case '|':
					i--
				case ' ':
				case '\t':
					break
				case '~':
					word += ' ';	// (&nbsp;)
					word += ' '
					i++
					continue
				case '-':
					ln = 1		// start of line
					break
				case '\\':
					if (!p[++i])
︙
313
314
315
316
317
318
319
320

321
322
323
324
325
326
327
313
314
315
316
317
318
319

320
321
322
323
324
325
326
327







-
+







			shift = s.wl
		} else if ((p[0] >= '0' && p[0] <= '9' && p.length > 2)
			|| p[1] == ':'
			|| p[0] == '(' || p[0] == ')') {
			if (p[0] == '(') {
				sz = spw
			} else {
				j = p.indexOf(' ');	// (&nbsp;)
				j = p.indexOf(' ')
				set_font(ly.font)
				if (j > 0)
					sz = strwh(p.slice(0, j))[0]
				else
					sz = w * .2
			}
			w -= sz
︙
382
383
384
385
386
387
388
389

390
391
392
393
394
395
396
382
383
384
385
386
387
388

389
390
391
392
393
394
395
396







-
+







	}
} // ly_set()

/* -- draw the lyrics under (or above) notes -- */
/* (the staves are not yet defined) */
function draw_lyric_line(p_voice, j, y) {
	var	p, lastx, w, s, s2, ly, lyl, ln,
		hyflag, lflag, x0, font, shift
		hyflag, lflag, x0, shift

	if (p_voice.hy_st & (1 << j)) {
		hyflag = true;
		p_voice.hy_st &= ~(1 << j)
	}
	for (s = p_voice.sym; /*s*/; s = s.next)
		if (s.type != C.CLEF
︙
412
413
414
415
416
417
418
419

420
421
422
423
424
425
426
412
413
414
415
416
417
418

419
420
421
422
423
424
425
426







-
+







					lflag = false;
					lastx = s.x + s.wr
				}
			}
			continue
		}
		if (ly.font != gene.curfont)		/* font change */
			gene.curfont = font = ly.font;
			gene.curfont = ly.font
		p = ly.t;
		w = p.wh[0]
		shift = ly.shift
		if (hyflag) {
			if (ly.ln == 2 && p == "_") {	// '_'
				p = "-"
			} else if (ly.ln != 2) {	// not '-'
︙
446
447
448
449
450
451
452

453
454
455


456
457
458
459
460
461
462
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465







+



+
+







			continue
		}
		x0 = s.x - shift;
		if (ly.ln)			// '-' at end
			hyflag = true
		if (user.anno_start || user.anno_stop) {
			s2 = {
				p_v: s.p_v,
				st: s.st,
				istart: ly.istart,
				iend: ly.iend,
				ts_prev: s,
				ts_next: s.ts_next,
				x: x0,
				y: y,
				ymn: y,
				ymx: y + gene.curfont.size,
				wl: 0,
				wr: w
			}
︙
Changes to core/mei.js.
︙
696
697
698
699
700
701
702
703


704
705
706
707
708
709
710
696
697
698
699
700
701
702

703
704
705
706
707
708
709
710
711







-
+
+







					curr.beam = 2
					break
				}
				sym_link(s)
			}
		}
		if (id) {
			note.s = s
			note.s = s			// chord
			note.m = s.nhd			// note index in the chord
			do_delayed(id, curr.chord ? note : s)
		}
		note.dur = dur
		if (acc_ty[tag.accid])
			note.acc = acc_ty[tag.accid]

//fixme: may have notes of the chord on different staves
︙
1015
1016
1017
1018
1019
1020
1021
1022

1023
1024
1025
1026
1027
1028



1029




1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1016
1017
1018
1019
1020
1021
1022

1023






1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035


1036
1037
1038
1039
1040
1041
1042







-
+
-
-
-
-
-
-
+
+
+

+
+
+
+




-
-







		case "below": ty = C.SL_BELOW; break
		}
		switch (tag.lform) {
		case "dashed":
		case "dotted": ty |= C.SL_DOTTED; break
		}

		sl = { ty: ty }
		sl = {
		if (s2.s) {
			sl.is_note = true	// to note
			sl.note = s2
		} else {
			s2.notes[0].s = s2
			sl.note = s2.notes[0]	// to chord
			ty: ty,
			ss: s1.s || s1,
			se: s2.s || s2
		}
		if (s1.s)
			sl.nts = s1
		if (s2.s)
			sl.nte = s2	// to note

		if (!s1.sls)
			s1.sls = []
		s1.sls.push(sl)
		if (s1.s)			// if from note of chord
			s1.s.sl1 = true
//fixme: ko if grace note
	}, // slur()

	sourceDesc: function(tag) {
		return true	// skip
	},

︙
Changes to core/modules.js.
1
2
3

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

25
26
27
28
29
30
31
1
2

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

24
25
26
27
28
29
30
31


-
+




















-
+







// abc2svg - modules.js - module handling
//
// Copyright (C) 2018-2020 Jean-Francois Moine
// Copyright (C) 2018-2022 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// empty function
if (!abc2svg.loadjs) {
    abc2svg.loadjs = function(fn, onsuccess, onerror) {
	if (onerror)
		onerror()
		onerror(fn)
    }
}

abc2svg.modules = {
	ambitus: {},
	begingrid: { fn: 'grid3' },
	beginps: { fn: 'psvg' },
︙
49
50
51
52
53
54
55

56
57
58
59
60
61
62
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63







+







	pedline: {},
	percmap: { fn: 'perc' },
	roman: {},
	soloffs: {},
	sth: {},
	strtab: {},
	temperament: { fn: 'temper' },
	tropt: {},

	nreq: 0,
	hooks: [],
	g_hooks: [],

	// scan the file and find the required modules
	// @file: ABC file
︙
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
80
81
82
83
84
85
86




87
88
89
90
91
92
93







-
-
-
-







		} // get_errmsg()

		// call back functions for loadjs()
		function load_end() {
			if (--abc2svg.modules.nreq == 0)
				abc2svg.modules.cbf()
		}
		function load_ko(fn) {
			abc2svg.modules.errmsg('Error loading the module ' + fn)
			load_end()
		}

		// test if some keyword in the file
	    var	m, i, fn,
		nreq_i = this.nreq,
		ls = file.match(/(^|\n)(%%|I:).+?\b/g)

		if (!ls)
︙
109
110
111
112
113
114
115
116





117
118
119
120
106
107
108
109
110
111
112

113
114
115
116
117
118
119
120
121







-
+
+
+
+
+





			// load the module
			if (m.fn)
				fn = m.fn
			this.nreq++
			abc2svg.loadjs(fn + "-1.js",
					load_end,
					function() {load_ko(fn)})
					function () {
						abc2svg.modules.errmsg(
							'Error loading the module ' + fn)
						load_end()
					})
		}
		return this.nreq == nreq_i
	}
} // modules
Changes to core/music.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - music.js - music generation
//
// Copyright (C) 2014-2022 Jean-Francois Moine
// Copyright (C) 2014-2023 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
236
237
238
239
240
241
242
243
244

245
246
247
248
249
250
251
236
237
238
239
240
241
242


243
244
245
246
247
248
249
250







-
-
+








	if (p1 > ps + 4) {		// if interval greater than a sixth
		if (dx1 > dx2)
			dx2 = dx1	// align the accidentals
		notes[i1].shac = notes[i2].shac = dx2
	} else {
		notes[i1].shac = dx1
		if (p1 != ps)
			dx1 += 7	// (there is a shift of a head when unison)
		dx1 += 7
		notes[i2].shac = dx2 = dx1
	}
	dx2 += 7

	// shift the remaining accidentals
	for (i = i1; --i > i2; ) {		// from top to bottom
		acc = notes[i].acc
︙
310
311
312
313
314
315
316
317
318




319
320
321
322
323
324
325
309
310
311
312
313
314
315


316
317
318
319
320
321
322
323
324
325
326







-
-
+
+
+
+







			continue
		}

		dx_head = dx_tb[s.head]

		// build a pseudo chord and shift the accidentals
		notes = []
		for ( ; s != s2; s = s.ts_next)
			Array.prototype.push.apply(notes, s.notes)
		for ( ; s != s2; s = s.ts_next) {
			if (!s.invis)
				Array.prototype.push.apply(notes, s.notes)
		}
		notes.sort(abc2svg.pitcmp)
		acc_shift(notes, dx_head)
	}
}

// link a symbol before an other one
function lkvsym(s, next) {	// voice linkage
︙
690
691
692
693
694
695
696
697

698
699
700
701
702
703
704
691
692
693
694
695
696
697

698
699
700
701
702
703
704
705







-
+







	}

	switch (s.type) {
	case C.NOTE:
	case C.REST:

		/* set the note widths */
		s.wr = wlnote = hw_tb[s.head]
		s.wr = wlnote = s.invis ? 0 : hw_tb[s.head]

		/* room for shifted heads and accidental signs */
		if (s.xmx > 0)
			s.wr += s.xmx + 4;
		for (s2 = s.prev; s2; s2 = s2.prev) {
			if (w_tb[s2.type])
				break
︙
801
802
803
804
805
806
807
808
809

810
811
812


813
814
815
816
817
818
819
820
821
822
823
824
825
826




827

828
829
830
831
832
833
834
802
803
804
805
806
807
808


809
810


811
812
813
814
815
816
817
818
819
820
821
822
823



824
825
826
827
828
829
830
831
832
833
834
835
836







-
-
+

-
-
+
+











-
-
-
+
+
+
+

+







						wlw = 14
				}
				break
			case C.CLEF:		/* extra space at start of line */
				if (s2.second
				 || s2.clef_small)
					break
				wlw += 8
				break
				// fall thru
			case C.KEY:
/*			case C.METER:	*/
				wlw += 4
			case C.METER:
				wlw += 3
				break
			}
		}

		/* leave room for guitar chord */
		if (s.a_gch)
			wlw = gchord_width(s, wlnote, wlw)

		// ignore the lyrics for now

		/* if preceeded by a grace note sequence, adjust */
		if (s2 && s2.type == C.GRACE)
			s.wl = wlnote - 4.5
		else
		if (s.prev && s.prev.type == C.GRACE) {
			s.prev.wl += wlnote - 4.5
			s.wl = s.prev.wl
		} else {
			s.wl = wlw
		}
		return
	case C.SPACE:
		xx = s.width / 2;
		s.wr = xx
		if (s.a_gch)
			xx = gchord_width(s, xx, xx)
		if (s.a_dd)
︙
864
865
866
867
868
869
870
871

872
873
874




875
876
877
878
879
880
881
866
867
868
869
870
871
872

873



874
875
876
877
878
879
880
881
882
883
884







-
+
-
-
-
+
+
+
+







			 && s.next.type != C.METER)
				s.wr = 7
			else
				s.wr = 5
//			s.notes[0].shhd = (w - 5) * -.5

			/* if preceeded by a grace note sequence, adjust */
			for (s2 = s.prev; s2; s2 = s2.prev) {
			s2 = s.prev
				if (w_tb[s2.type]) {
					if (s2.type == C.GRACE)
						s.wl -= 6
			if (s2 && s2.type == C.GRACE)
				s.wl -= 6
			for ( ; s2; s2 = s2.prev) {
				if (w_tb[s2.type]) {
					if (s2.type == C.STBRK)
						s.wl -= 12
					break
				}
			}

		if (s.a_dd)
︙
894
895
896
897
898
899
900
901

902
903
904
905
906
907
908
909
910
911
912
913
914
915


916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936








937
938
939
940
941
942
943
944
945
946
947
948


949


950
951
952
953



954
955
956
957
958
959
960
897
898
899
900
901
902
903

904
905
906
907
908


909
910
911
912
913
914


915
916

917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934


935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961

962
963
964
965
966
967
968
969
970
971
972







-
+




-
-






-
-
+
+
-


















-
-
+
+
+
+
+
+
+
+












+
+

+
+


-

+
+
+







	case C.CLEF:
// (there may be invisible clefs in empty staves)
		if (s.invis) {
			s.wl = s.wr = 1		// (!! not 0 !!)
			return
		}
		s.wl = s.clef_small ? 11 : 12
		s.wr = s.clef_small ? 10 : 12
		s.wr = s.clef_small ? 8 : 13
		if (s.prev && s.prev.type == C.STBRK) {
			s.wl -= 6
			delete s.next.clef_small
		}
		if (s.next && s.next.type == C.BAR)
			s.wr -= 4
		return
	case C.KEY:
		if (s.invis) {				// if no accidental
			s.wl = s.wr = 0			// no width
			return
		}
		s.wl = 3;
		esp = 4
		s.wl = 0
		esp = 3
		if (!s.k_a_acc) {
			n1 = s.k_sf			/* new key sig */
			if (s.k_old_sf && (s.fmt.cancelkey || n1 == 0))
				n2 = s.k_old_sf	/* old key */
			else
				n2 = 0
			if (n1 * n2 >= 0) {		/* if no natural */
				if (n1 < 0)
					n1 = -n1
				if (n2 < 0)
					n2 = -n2
				if (n2 > n1)
					n1 = n2
			} else {
				n1 -= n2
				if (n1 < 0)
					n1 = -n1;
				esp += 3	/* see extra space in draw_keysig() */
			}
		} else {
			n1 = n2 = s.k_a_acc.length
		if (s.k_bagpipe == 'p')		// K:Hp - add the g natural
			n1++
		if (s.k_a_acc) {
			n2 = s.k_a_acc.length
			if (s.exp)
				n1 = n2			// no key signature
			else
				n1 += n2
			if (n2)
				last_acc = s.k_a_acc[0].acc
			for (i = 1; i < n2; i++) {
				acc = s.k_a_acc[i]
				if (acc.pit > s.k_a_acc[i - 1].pit + 6
				 || acc.pit < s.k_a_acc[i - 1].pit - 6)
					n1--		// no clash
				else if (acc.acc != last_acc)
					esp += 3;
				last_acc = acc.acc
			}
		}
		if (!n1)
			break			// no width
		s.wr = 5.5 * n1 + esp
		if (s.prev && !s.prev.bar_type)
			s.wl += 2
		return
	case C.METER:
		wlw = 0;
		s.x_meter = []
		if (!s.a_meter.length)
			break				// no width
		wlw = 0
		for (i = 0; i < s.a_meter.length; i++) {
			meter = s.a_meter[i]
			switch (meter.top[0]) {
			case 'C':
			case 'c':
			case 'o':
				s.x_meter[i] = wlw + 6;
︙
985
986
987
988
989
990
991
992
993


994
995
996
997
998
999
1000
997
998
999
1000
1001
1002
1003


1004
1005
1006
1007
1008
1009
1010
1011
1012







-
-
+
+







						break
					}
				}
				s.x_meter[i] = wlw + w / 2
				wlw += w
			}
		}
		s.wl = 0;
		s.wr = wlw + 6
		s.wl = 1
		s.wr = wlw + 7
		return
	case C.MREST:
		s.wl = 6;
		s.wr = 66
		return
	case C.GRACE:
		if (s.invis)
︙
1106
1107
1108
1109
1110
1111
1112
1113

1114
1115
1116
1117
1118
1119
1120
1118
1119
1120
1121
1122
1123
1124

1125
1126
1127
1128
1129
1130
1131
1132







-
+








	while (!s.dur) {
		switch (s.type) {
		case C.BAR:
			// (hack to have quite the same note widths between measures)
			if (!s.next)
				space *= .9
			return space * .9 - 7
			return space * .9 - 3
		case C.CLEF:
			return space - s.wl - s.wr
		case C.BLOCK:			// no space
		case C.REMARK:
		case C.STAVES:
		case C.TEMPO:
			s = s.ts_next
︙
1225
1226
1227
1228
1229
1230
1231
1232


1233
1234
1235
1236
1237
1238
1239

1240
1241
1242
1243
1244
1245
1246

1247
1248
1249
1250
1251

1252
1253


1254
1255
1256
1257
1258
1259
1260
1261
1262
1263

1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275



1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289


1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302

1303


1304
1305



1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323



1324
1325
1326

1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342

1343
1344
1345
1346
1347
1348
1349
1237
1238
1239
1240
1241
1242
1243

1244
1245
1246
1247
1248
1249
1250
1251

1252
1253
1254
1255
1256
1257
1258

1259
1260
1261
1262
1263
1264
1265


1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305


1306
1307
1308







1309
1310
1311
1312
1313
1314

1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344

1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360

1361
1362
1363
1364
1365
1366
1367
1368







-
+
+






-
+






-
+





+
-
-
+
+










+












+
+
+












-
-
+
+

-
-
-
-
-
-
-





+
-
+
+


+
+
+


















+
+
+


-
+















-
+







		prev: s,
		fmt: s.fmt
	}
} // _bar()

// create an invisible bar for end of music lines
function add_end_bar(s) {
    var b = _bar(s)
    var b = _bar(s),
	sn = s.ts_next		// start of next line

	b.wl = 0
	b.wr = 0
	b.ts_prev = s
	b.next = s.next
	b.ts_next = s.ts_next
	b.shrink = s.wr + 3
	b.shrink = s.type == C.STBRK ? 0 : (s.wr + 3)

	if (s.next)			// (must not be the end of the voice)
		s.next.prev = b
//	if (s.ts_next)
		s.ts_next.ts_prev = b
	s.next = s.ts_next = b
	b.space = set_space(b, s.time + s.dur * .4)
	b.space = sn.space * .9 - 3
	return b
}

/* -- set the width and space of all symbols -- */
// this function is called once for the whole tune
// and once more for each new music line
function set_allsymwidth() {
    var	val, st, s_chs, stup,
function set_allsymwidth(first) {
    var	val, st, s_chs, stup, itup,
	s = tsfirst,
	s2 = s,
	xa = 0,
	xl = [],
	wr = [],
	maxx = xa,
	tim = s.time

	/* loop on all symbols */
	while (1) {
		itup = 0
		do {
			if ((s.a_gch || s.a_ly) && !s_chs)
				s_chs = s;
			self.set_width(s);
			st = s.st
			if (xl[st] == undefined)
				xl[st] = 0
			if (wr[st] == undefined)
				wr[st] = 0;
			val = xl[st] + wr[st] + s.wl
			if (val > maxx)
				maxx = val
			if (s.dur && s.dur != s.notes[0].dur	// if in tuplet
			 && first)			// (first time only)
				itup = 1
			s = s.ts_next
		} while (s && !s.seqst);

		// set the spaces of the time sequence
		s2.shrink = maxx - xa
		s2.space = s2.ts_prev ? set_space(s2, tim) : 0

		// adjust the spacing when after a spacer (y)
		if (s2.space == 0 && s2.ts_prev
		 && s2.ts_prev.type == C.SPACE && s2.ts_prev.seqst)
			s2.space = s2.ts_prev.space /= 2

		if (s2.dur && s2.dur != s2.notes[0].dur) {
			if (!stup) {
		if (itup) {
			if (!stup)
				stup = s2
				while (stup.prev
				 && stup.prev.dur
				 && stup.prev.dur != stup.prev.notes[0].dur)
					stup = stup.prev
				while (!stup.seqst)
					stup = stup.ts_prev
			}
		} else if (stup && stup.v == s2.v) {
			set_sp_tup(stup, s2)
			stup = null
		}

		if (!s2.shrink) {
		if (!s2.shrink && !s2.space && s2.type == C.CLEF) {
		    if (!s2.space && s2.type == C.CLEF
		     && !s2.ts_prev.bar_type) {
			delete s2.seqst;		/* no space */
			s2.time = tim
		    } else if (s2.dur) {
			s2.shrink = 10			// cannot be null
		    }
		}
		tim = s2.time
		if (!s)
			break

		// update the min left space per staff
		for (st = 0; st < wr.length; st++)
			wr[st] = 0;
		xa = maxx
		do {
			st = s2.st;
			xl[st] = xa
			if (s2.wr > wr[st])
				wr[st] = s2.wr
			s2 = s2.ts_next
		} while (!s2.seqst)
	}

	if (stup)
		set_sp_tup(stup, s2)

	// let the chord symbols at the same offset
	// and adjust the spacing due to the lyrics
	if (s_chs)
	if (first && s_chs)
		set_w_chs(s_chs)
}

// insert a rest, this one replacing a sequence or a measure
function to_rest(so) {
    var	s = clone(so)

	s.prev.next = so.ts_prev = so.prev = s.ts_prev.ts_next = s
	s.next = s.ts_next = so
	so.seqst = false
	so.invis = so.play = true

	s.type = C.REST
// just keep nl and seqst
	delete s.in_tuplet
	delete s.sl1
	delete s.tp
	delete s.a_dd
	delete s.a_gch
	delete s.sls
//fixme: what if chord / slur in notes / ... ?
/*fixme: should set many parameters for set_width*/
//	set_width(s)
	return s
︙
1594
1595
1596
1597
1598
1599
1600
1601

1602
1603
1604
1605
1606
1607
1608
1613
1614
1615
1616
1617
1618
1619

1620
1621
1622
1623
1624
1625
1626
1627







-
+







/* -- define the beginning of a new music line -- */
function set_nl(s) {			// s = start of line
    var	p_voice, done, tim, ptyp

	// divide the left repeat (|:) or variant bars (|1)
	// the new bars go in the next line
	function bardiv(so) {		// start of next line
	    var s, s1, s2, t1, t2, i, noseq
	    var s, s1, s2, t1, t2, i

	    function new_type(s) {
	    var	t = s.bar_type.match(/(:*)([^:]*)(:*)/)
			// [1] = starting ':'s, [2] = middle, [3] = ending ':'s

		if (!t[3]) {		// if start of variant
			// |1 -> | [1
︙
1621
1622
1623
1624
1625
1626
1627
1628







1629

1630
1631


1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653

1654
1655
1656
1657
1658
1659
1660
1661



1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672

1673
1674

1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1640
1641
1642
1643
1644
1645
1646

1647
1648
1649
1650
1651
1652
1653
1654
1655


1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674


1675
1676
1677
1678
1679
1680
1681
1682
1683
1684


1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697

1698


1699



1700
1701
1702
1703
1704
1705
1706







-
+
+
+
+
+
+
+

+
-
-
+
+

















-
-



+






-
-
+
+
+










-
+
-
-
+
-
-
-







		}
	    } // new_typ()

		s = so				// start of sequence
		while (s.ts_prev
		 && s.ts_prev.time == so.time) {
			s = s.ts_prev
			if (s.bar_type)
			if (s.bar_type) {

				// if repeat variant, move the new line
				if (s.bar_type[0] == '['
				 && s.text
				 && s.ts_next == so)
					so = s
				s1 = s		// first previous bar
			}
			if (s.type == C.GRACE)
				so = s		// keep the grace notes in the next line
//			if (s.type == C.GRACE)
//				so = s		// keep the grace notes in the next line
		}
		if (!s1)
			return so

		// search the insertion point in the new line
		for (s = so; ; s = s.ts_next) {
			switch (s.type) {
//			default:
//				if (w_tb[s.type])
//					break
			case C.KEY:
			case C.METER:
			case C.CLEF:
				continue
			}
			break
		}
		if (s.bar_type)
			noseq = 1

		while (s1 != so) {
		    if (s1.bar_type
		     && s1.next
		     && (s1.bar_type.slice(-1) == ':'
		      || s1.text)
		     && s1.bar_type != ':') {
			new_type(s1)		// return t1 and t2
			s2 = clone(s1)
			s1.bar_type = t1
			delete s1.rbstop
			delete s1.a_gch
			delete s1.a_gch
			if (t1 == '|')
				delete s1.rbstop // (needed for play variant)
			s2.bar_type = t2
			lkvsym(s2, s1.next)	// voice
			while (1) {
				if (s.type != C.BAR
//fixme: pb if inverted voices
				 || s.v > s2.v)
					break
				s = s.ts_next
			}
			lktsym(s2, s)		// time
			if (!noseq) {
			if (s1.seqst)
				noseq = 1
				s2.seqst = true
				s2.seqst = 1
				s2.shrink = s2.wl + s2.prev.wr
				s2.space = 0
			}
			if (s == so)
				so = s2
			if (s1.text) {
				s2.invis = true
				delete s1.text
				delete s1.rbstart
			}
︙
1699
1700
1701
1702
1703
1704
1705
1706

1707
1708
1709
1710
1711
1712
1713
1721
1722
1723
1724
1725
1726
1727

1728
1729
1730
1731
1732
1733
1734
1735







-
+







		if (s.type != C.BAR)
			add_end_bar(s)
	} // set_eol()

	// put the warning symbols
	// the new symbols go in the previous line
	function do_warn(s) {		// start of next line
	    var s1, s2, s3
	    var s1, s2, s3, s4

		// advance in the next line
		for (s2 = s; s2; s2 = s2.ts_next) {
			switch (s2.type) {
			case C.KEY:
				if (!s.fmt.keywarn
				 || s2.invis)
︙
1724
1725
1726
1727
1728
1729
1730


1731
1732
1733



1734
1735
1736
1737
1738
1739
1740
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767







+
+



+
+
+







					s1 = s.ts_prev
				}
				// fall thru
			case C.CLEF:
				if (!s2.prev)		// start of voice
					continue
				if (s2.type == C.CLEF) {
					if (s2.invis)	// if 'K: clef=none' after bar
						break
					for (s1 = s.ts_prev; s1; s1 = s1.ts_prev) {
						switch (s1.type) {
						case C.BAR:
							if (s1.bar_type[0] == ':')
								break
							// fall thru
						case C.KEY:
						case C.METER:
							continue
						}
						break
					}
				}
︙
1750
1751
1752
1753
1754
1755
1756

1757


1758
1759
1760
1761







1762
1763
1764
1765
1766
1767
1768
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787




1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801







+

+
+
-
-
-
-
+
+
+
+
+
+
+







					if (s1.v == s2.v)
						break
				}
				lkvsym(s3, s1)		// voice link

				// care with spacing
				if (s3.seqst) {
					self.set_width(s3)
					s3.shrink = s3.wl
					s4 = s3.ts_prev
					while (!s4.seqst)
					if (s3.prev)	// if not start of voice
						s3.shrink += s3.prev.wr
					s3.space = set_space(s3, s3.ts_prev.time)
					s3.next.shrink = s3.wr + s3.next.wl
						s4 = s4.ts_prev
					s3.shrink += s4.wr
					s3.space = 0
					s4 = s3.ts_next
					while (!s4.seqst)
						s4 = s4.ts_next
					s4.shrink = s3.wr + s4.wl
				}
				delete s3.part
				continue
			}
			if (w_tb[s2.type])
				break		// symbol with a width
		}
︙
1881
1882
1883
1884
1885
1886
1887
1888

1889
1890
1891

1892
1893
1894
1895
1896
1897
1898
1899
1900
1901

1902
1903
1904
1905
1906
1907
1908
1914
1915
1916
1917
1918
1919
1920

1921
1922
1923

1924
1925
1926
1927
1928
1929
1930
1931
1932
1933

1934
1935
1936
1937
1938
1939
1940
1941







-
+


-
+









-
+







}

/* -- search where to cut the lines according to the staff width -- */
function set_lines(	s,		/* first symbol */
			next,		/* symbol of the next line / null */
			lwidth,		/* w - (clef & key sig) */
			indent) {	/* for start of tune */
    var	first, s2, s3, s4, x, xmin, xmid, xmax, wwidth, shrink, space,
    var	first, s2, s3, s4, s5, x, xmin, xmid, xmax, wwidth, shrink, space,
	nlines,
	last = next ? next.ts_prev : null,
	ws = get_width(s, next)		// 2 widths: nice and shrinked
	ws = get_width(s, next)		// 2 widths: nice and shrunk

	// take care of big key signatures at end of line
	if (s.fmt.keywarn && next
	 && next.type == C.KEY && !last.dur) {
		ws[0] += next.wr
		ws[1] += next.wr
	}

	// check if the symbols can enter in one line
	if (ws[1] + indent < lwidth) {
	if (ws[0] + indent < lwidth) {
		if (next)
			next = set_nl(next)
		return next || last
	}

	/* loop on cutting the tune into music lines */
	wwidth = ws[0] + indent
︙
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935



1936
1937
1938
1939
1940
1941
1942
1959
1960
1961
1962
1963
1964
1965



1966
1967
1968
1969
1970
1971
1972
1973
1974
1975







-
-
-
+
+
+







				s2 = s
			if (s.x >= xmin)
				break
		}
		s4 = s			// keep first symbol with x greater than xmin
//fixme: can this occur?
		if (s == next) {
			if (next)
				next = set_nl(next)
			return next
			if (s)
				s = set_nl(s)
			return s
		}

		/* try to cut on a measure bar */
		s3 = null
		for ( ; s != next; s = s.ts_next) {
			x = s.x
			if (!x)
︙
1960
1961
1962
1963
1964
1965
1966

1967
1968

1969
1970
1971
1972
1973
1974

1975
1976
1977
1978
1979



1980
1981
1982
1983


1984
1985
1986

1987
1988
1989










1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003


2004
2005
2006
2007
2008
2009
2010
1993
1994
1995
1996
1997
1998
1999
2000
2001

2002






2003





2004
2005
2006




2007
2008

2009
2010
2011



2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033


2034
2035
2036
2037
2038
2039
2040
2041
2042







+

-
+
-
-
-
-
-
-
+
-
-
-
-
-
+
+
+
-
-
-
-
+
+
-


+
-
-
-
+
+
+
+
+
+
+
+
+
+












-
-
+
+







		if (!s3) {
			s = s4			// restart after xmin

		    var	beam = 0,
			bar_time = s2.time

			xmax -= 8; // (left width of the inserted bar in set_allsymwidth)
			s5 = s
			for ( ; s != next; s = s.ts_next) {
				if (s.dur) {
				if (s.seqst) {
					if (!s.beam_end)
						beam |= 1 << s.v
					else
						beam &= ~(1 << s.v)
				}
				x = s.x
					x = s.x
				if (!x)
					continue
				if (x + s.wr >= xmax)
					break
				if (beam || s.in_tuplet)
					if (x + s.wr >= xmax)
						break
					if (!beam && !s.in_tuplet
					continue
				if (s3 && s.x >= xmid) {
					if (xmid - s3.x > s.x - xmid
					 || (s.dur
					 && (xmid - s5.x > x - xmid
					  || (s.time - bar_time)
					  && (s.time + s.dur - bar_time)
							% (C.BLEN / 4) == 0))
						s3 = s
				}
					break
				}
				s3 = s
				if (s.beam_st)
					beam |= 1 << s.v
				if (s.beam_end)
					beam &= ~(1 << s.v)
				s5 = s		// start of new time sequence
			}
			if (s3) {
				do {		// cut on the previous sequence
					s3 = s3.ts_prev
				} while (!s3.seqst)
			}
		}

		// cut anyhere
		if (!s3) {
			s3 = s = s4
			for ( ; s != next; s = s.ts_next) {
				x = s.x
				if (!x)
					continue
				if (x + s.wr >= xmax)
					break
				if (s3 && s.x >= xmid) {
					if (xmid - s3.x > s.x - xmid)
				if (s3 && x >= xmid) {
					if (xmid - s3.x > x - xmid)
						s3 = s
					break
				}
				s3 = s
			}
		}
		s = s3
︙
2032
2033
2034
2035
2036
2037
2038
2039
2040


2041
2042
2043
2044
2045
2046
2047








2048
2049
2050
2051
2052
2053
2054
2055




2056
2057

2058
2059
2060
2061
2062


2063
2064
2065
2066
2067
2068
2069
2064
2065
2066
2067
2068
2069
2070


2071
2072







2073
2074
2075
2076
2077
2078
2079
2080
2081
2082






2083
2084
2085
2086


2087





2088
2089
2090
2091
2092
2093
2094
2095
2096







-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+


-
-
-
-
-
-
+
+
+
+
-
-
+
-
-
-
-
-
+
+







		wwidth -= s.x - first.x;
		indent = 0
	}
	return s
}

/* -- cut the tune into music lines -- */
function cut_tune(lwidth, indent) {
    var	s2, i,
function cut_tune(lwidth, lsh) {
    var	s2, i, mc,
//fixme: not usable yet
//		pg_sav = {
//			leftmargin: cfmt.leftmargin,
//			rightmargin: cfmt.rightmargin,
//			pagewidth: cfmt.pagewidth,
//			scale: cfmt.scale
//		},
	pg_sav = {			// save the page parameters
		leftmargin: cfmt.leftmargin,
		rightmargin: cfmt.rightmargin,
		pagewidth: cfmt.pagewidth,
		scale: cfmt.scale
	},
	indent = lsh[0] - lsh[1],	// extra width of the first line
	ckw = get_ck_width(),		// width of the starting symbols
	s = tsfirst

	// take care of the voice subnames
	if (indent) {
		i = set_indent()
		lwidth -= i;
		indent -= i;
	}
	lwidth -= lsh[1]		// width of the lines
	if (cfmt.indent && cfmt.indent > lsh[0])
		indent += cfmt.indent


	/* adjust the line width according to the starting clef
	// adjust the line width according to the starting symbols
	 * and key signature */
/*fixme: may change in the tune*/
	i = get_ck_width();
	lwidth -= i[0];
	indent += i[1]
	lwidth -= ckw[0]
	indent += ckw[1]

	if (cfmt.custos && voice_tb.length == 1)
		lwidth -= 12

	/* if asked, count the measures and set the EOLNs */
	i = s.fmt.barsperstaff
	if (i) {
︙
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096











2097
2098
2099














2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110


2111
2112
2113
2114
2115
2116
2117



2118
2119
2120
2121
2122
2123
2124
2125
2126







2127
2128
2129
2130
2131
2132
2133
2106
2107
2108
2109
2110
2111
2112











2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123



2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154



2155
2156
2157
2158
2159
2160






2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174







-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+











+
+




-
-
-
+
+
+



-
-
-
-
-
-
+
+
+
+
+
+
+







			i = s.fmt.barsperstaff
		}
	}

	/* cut at explicit end of line, checking the line width */
	s2 = s
	for ( ; s; s = s.ts_next) {
//fixme: not usable yet
//		if (s.type == C.BLOCK) {
//			switch (s.subtype) {
//			case "leftmargin":
//			case "rightmargin":
//			case "pagescale":
//			case "pagewidth":
//			case "scale":
//			case "staffwidth":
//				set_format(s.subtype, s.param)
//				break
		if (s.type == C.BLOCK) {
			switch (s.subtype) {
			case "leftmargin":
			case "rightmargin":
			case "pagescale":
			case "pagewidth":
			case "scale":
			case "staffwidth":
				if (!s.soln)
					self.set_format(s.subtype, s.param)
				break
//			}
//			continue
//		}
			case "mc_start":
				mc = {
					lm: cfmt.leftmargin,
					rm: cfmt.rightmargin
				}
				break
			case "mc_new":
			case "mc_end":
				cfmt.leftmargin = mc.lm
				cfmt.rightmargin = mc.rm
				img.chg = 1 //true
				break
			}
		}
		if (!s.ts_next) {
			s = null
		} else if (!s.soln) {
			continue
		} else {
			s.soln = false
			if (s.time == s2.time)
				continue	// empty music line!
			while (!s.seqst)
				s = s.ts_prev
		}
		set_page()
		lwidth = get_lwidth() - lsh[1] - ckw[0]
		s2 = set_lines(s2, s, lwidth, indent)
		if (!s2)
			break

//--fixme
//		s = s2.ts_prev;		// don't miss an eoln
		s = s2
		s = s2.type == C.BLOCK
			? s2.ts_prev		// don't miss a parameter
			: s
		indent = 0
	}

//fixme: not usable yet
//	// restore the page parameters at start of line
//	cfmt.leftmargin = pg_sav.leftmargin;
//	cfmt.rightmargin = pg_sav.rightmargin;
//	cfmt.pagewidth = pg_sav.pagewidth;
//	cfmt.scale = pg_sav.scale
	// restore the page parameters at start of line
	cfmt.leftmargin = pg_sav.leftmargin
	cfmt.rightmargin = pg_sav.rightmargin
	cfmt.pagewidth = pg_sav.pagewidth
	cfmt.scale = pg_sav.scale
	img.chg = 1
	set_page()
}

/* -- set the y values of some symbols -- */
function set_yval(s) {
//fixme: staff_tb is not yet defined
//	var top = staff_tb[s.st].topbar
//	var bot = staff_tb[s.st].botbar
︙
2501
2502
2503
2504
2505
2506
2507
2508

2509
2510
2511
2512
2513
2514
2515
2516
2542
2543
2544
2545
2546
2547
2548

2549

2550
2551
2552
2553
2554
2555
2556







-
+
-







		}

		/* go backwards and search where to insert a clef change */
		s3 = s
		for (s2 = s.ts_prev; s2 != s_last_chg; s2 = s2.ts_prev) {
			if (s2.st != st)
				continue
			if (s2.type == C.BAR
			if (s2.type == C.BAR) {
			 && s2.v == s.v) {
				s3 = s2.bar_type[0] != ':' ? s2 : s2.next
				break
			}
			if (s2.type != C.NOTE)
				continue

			/* have a 2nd choice on beam start */
︙
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2598
2599
2600
2601
2602
2603
2604


































2605
2606
2607
2608
2609
2610
2611







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







		}
		staff_tb[st] = {
			output: "",
			sc_out: ""
		}
	}

	// set the starting clefs of the staves
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (!sy.voices[v])
			continue
		st = sy.voices[v].st
		if (!sy.voices[v].second) {		// main voices
			sy.staves[st].staffnonote = p_voice.staffnonote
			if (p_voice.staffscale)
				sy.staves[st].staffscale = p_voice.staffscale
			if (sy.voices[v].sep)
				sy.staves[st].sep = sy.voices[v].sep
			if (sy.voices[v].maxsep)
				sy.staves[st].maxsep = sy.voices[v].maxsep;

			if (!p_voice.clef.clef_auto)
				delete staff_clef[st].autoclef
		}
	}
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (!sy.voices[v]
		 || sy.voices[v].second)		// main voices
			continue
		st = sy.voices[v].st;
		s = p_voice.clef
		if (staff_clef[st].autoclef) {
			s.clef_type = set_auto_clef(st,
						tsfirst,
						s.clef_type);
			s.clef_line = s.clef_type == 't' ? 2 : 4
		}
		staff_clef[st].clef = staff_tb[st].clef = s
	}
	for (st = 0; st <= sy.nstaff; st++)
		mid[st] = (sy.staves[st].stafflines.length - 1) * 3

	for (s = tsfirst; s; s = s.ts_next) {
		if (s.repeat_n)
			set_repeat(s)

︙
2646
2647
2648
2649
2650
2651
2652
2653

2654
2655
2656
2657
2658
2659
2660
2652
2653
2654
2655
2656
2657
2658

2659
2660
2661
2662
2663
2664
2665
2666







-
+







					new_line = new_type == 't' ? 2 : 4
				} else {
					new_type = s2.clef_type;
					new_line = s2.clef_line
				}
				if (!staff_clef[st].clef) {	// new staff
					if (s2.clef_auto) {
						if (s2.type != 'a')
						if (s2.clef_type != 'a')
							p_voice.clef =
								clone(p_voice.clef);
						p_voice.clef.clef_type = new_type;
						p_voice.clef.clef_line = new_line
					}
					staff_tb[st].clef =
						staff_clef[st].clef = p_voice.clef
︙
2766
2767
2768
2769
2770
2771
2772
2773

2774
2775
2776
2777
2778
2779
2780
2772
2773
2774
2775
2776
2777
2778

2779
2780
2781
2782
2783
2784
2785
2786







-
+







}

/* upper and lower space needed by rests */
var rest_sp = [
	[18, 18],
	[12, 18],
	[12, 12],
	[0, 12],
	[6, 12],
	[6, 8],
	[10, 10],			/* crotchet */
	[6, 4],
	[10, 0],
	[10, 4],
	[10, 10]
]
︙
3248
3249
3250
3251
3252
3253
3254
3255

3256
3257


3258
3259
3260
3261
3262
3263

3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282

3283
3284
3285
3286
3287
3288
3289
3290
3291
3254
3255
3256
3257
3258
3259
3260

3261
3262

3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274













3275
3276

3277


3278
3279
3280
3281
3282
3283
3284







-
+

-
+
+






+



-
-
-
-
-
-
-
-
-
-
-
-
-


-
+
-
-







	p_v.last_sym = s;

	lktsym(s, last_s)
}

/* -- init the symbols at start of a music line -- */
function init_music_line() {
   var	p_voice, s, s1, s2, s3, last_s, v, st, shr, shrmx, shl, fmt,
   var	p_voice, s, s1, s2, s3, last_s, v, st, shr, shrmx, shl,
	shlp, p_st, top,
	nv = voice_tb.length
	nv = voice_tb.length,
	fmt = tsfirst.fmt

	/* initialize the voices */
	for (v = 0; v < nv; v++) {
		if (!cur_sy.voices[v])
			continue
		p_voice = voice_tb[v];
		p_voice.st = cur_sy.voices[v].st
		p_voice.second = cur_sy.voices[v].second;
		p_voice.last_sym = p_voice.sym;

		/* move the voice to a printed staff */
		st = cur_sy.voices[v].st
		while (st <= nstaff && !cur_sy.st_print[st])
			st++
		if (st > nstaff)
			continue		// no printed staff
		p_voice.st = st
		if (!p_voice.second) {
			staff_tb[st].key = p_voice.ckey
			staff_tb[st].meter = p_voice.meter
		}
	}

	// move the first clefs, key signatures and time signatures
	// to the staves
	s = tsfirst
	   for (s = p_voice.sym; s && s.time == tsfirst.time; s = s.next) {
	fmt = s.fmt
	while (s) {
		switch (s.type) {
		case C.CLEF:
		case C.KEY:
		case C.METER:
			switch (s.type) {
			case C.CLEF:
				staff_tb[s.st].clef = s
︙
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312

3313
3314
3315
3316
3317
3318
3319
3295
3296
3297
3298
3299
3300
3301

3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312







-



+







				break
			}
			unlksym(s)
			// fall thru
		case C.TEMPO:
		case C.BLOCK:
		case C.REMARK:
			s = s.ts_next
			continue
		}
		break
	    }
	}

	// generate the starting clefs, key signatures and time signatures

	// add a clef at start of the main voices
	last_s = tsfirst
	for (v = 0; v < nv; v++) {
︙
3343
3344
3345
3346
3347
3348
3349
3350

3351
3352
3353

3354
3355

3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374

3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391

3392
3393
3394
3395
3396
3397
3398
3336
3337
3338
3339
3340
3341
3342

3343
3344


3345


3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391







-
+

-
-
+
-
-
+



















+

















+







			s.ts_prev = null
		if (!s.ts_prev) {
			tsfirst = s;
		} else {
			s.ts_prev.ts_next = s
			delete s.seqst
		}
		if (last_s) {
		if (last_s)
			last_s.ts_prev = s
			if (last_s.type == C.CLEF)
				delete last_s.seqst
		delete s.clef_small;
		}
		delete s.clef_small;
		delete s.part
		s.second = cur_sy.voices[v].second
// (fixme: needed for sample5 X:3 Fugue & staffnonote.xhtml)
		if (!cur_sy.st_print[st])
			s.invis = true
		s.fmt = fmt
	}

	/* add keysig */
	for (v = 0; v < nv; v++) {
		if (!cur_sy.voices[v]
		 || cur_sy.voices[v].second
		 || !cur_sy.st_print[cur_sy.voices[v].st])
			continue
		p_voice = voice_tb[v]
		s2 = p_voice.ckey
		if (s2.k_sf || s2.k_a_acc) {
			s = clone(s2)
			new_sym(s, p_voice, last_s)
			delete s.invis
			delete s.part
			s.k_old_sf = s2.k_sf	// no key cancel
			s.fmt = fmt
		}
	}

	/* add time signature (meter) if needed */
	if (insert_meter) {
		for (v = 0; v < nv; v++) {
			p_voice = voice_tb[v];
			s2 = p_voice.meter
			if (!cur_sy.voices[v]
			 || cur_sy.voices[v].second
			 || !cur_sy.st_print[cur_sy.voices[v].st])
//			 || !s2.a_meter.length)
				continue
			s = clone(s2)
			new_sym(s, p_voice, last_s)
			delete s.part
			s.fmt = fmt
		}
		insert_meter = false		// no meter any more
	}

	// add an invisible bar for the various continued elements
	for (v = 0; v < nv; v++) {
︙
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469



3470
3471
3472


3473
3474
3475

3476
3477
3478
3479
3480

3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3436
3437
3438
3439
3440
3441
3442




















3443
3444
3445



3446
3447



3448





3449



3450
3451
3452
3453
3454
3455
3456







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
-
-
-
+
-
-
-
-
-
+
-
-
-







	}

	// compute the spacing of the added symbols
	self.set_pitch(last_s);

	s = tsfirst
	s.seqst = true
	shlp = 0
	while (1) {
		s2 = s;
		shl = shrmx = 0
		do {
			self.set_width(s);
			shr = s.wl + shlp
			if (shr > shrmx)
				shrmx = shr;
			if (s.wr > shl)
				shl = s.wr	// left width for next symbol
			s = s.ts_next
		} while (s != last_s && !s.seqst);
		s2.shrink = shrmx;
		s2.space = 0
		if (s == last_s)
			break
		shlp = shl
	}


	for (s = last_s; s.ts_next && !s.ts_next.seqst; s = s.ts_next)
		;
	// update the spacing before the first old time sequence
	shr = 0
	if (last_s.type == C.BAR && s2.type == C.CLEF)
	s2 = s.ts_next
	s.ts_next = null
		shl += 4		// (see set_width)
	do {
		self.set_width(s)
	set_allsymwidth()
		if (s.a_ly)
			ly_set(s)
		if (shr < s.wl)
			shr = s.wl;
		s = s.ts_next
	s.ts_next = s2
	} while (s && !s.seqst);
	last_s.shrink = shl + shr
	last_s.space = 0
} // init_music_line()

// check if the tune ends on a measure bar
function check_end_bar() {
    var	s2,
	s = tsfirst
	while (s.ts_next)
︙
3501
3502
3503
3504
3505
3506
3507
















3508
3509
3510
3511
3512
3513
3514
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







// and sort the pitches in the chords
// and build the chord symbols / annotations
// this function is called only once per tune
function set_words(p_voice) {
	var	s, s2, nflags, lastnote, res,
		start_flag = true,
		pitch = 127			/* no note */

	// adjust the duration of the notes in a decoration !trem1..4!
	function trem_adj(s) {
		s.prev.trem2 = true
		s.prev.head = ++s.head
		if (--s.nflags > 0) {
			s.nflags += s.ntrem
		} else {
			if (s.nflags <= -2) {
				s.stemless = true
				s.prev.stemless = true
			}
			s.nflags = s.ntrem
		}
		s.prev.nflags = s.nflags
	} // trem_adj()

	for (s = p_voice.sym; s; s = s.next) {
		if (s.type == C.NOTE) {
			pitch = s.notes[0].pit
			break
		}
	}
︙
3527
3528
3529
3530
3531
3532
3533
3534

3535








3536
3537
3538
3539
3540
3541
3542
3509
3510
3511
3512
3513
3514
3515

3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532







-
+

+
+
+
+
+
+
+
+







				start_flag = true
			if (!s.next && s.prev
			 && !s.invis
			 && s.prev.head == C.OVALBARS)
				s.prev.head = C.SQUARE
			break
		case C.GRACE:
			for (s2 = s.extra; s2; s2 = s2.next)
			for (s2 = s.extra; s2; s2 = s2.next) {
				s2.notes.sort(abc2svg.pitcmp)
				res = identify_note(s2, s2.dur_orig)
				s2.head = res[0]
				s2.dots = res[1]
				s2.nflags = res[2]
				if (s2.trem2
				 && (!s2.next || s2.next.trem2))
					trem_adj(s2)
			}
			break
		case C.NOTE:
		case C.REST:
			res = identify_note(s, s.dur_orig);
			s.head = res[0];
			s.dots = res[1];
			s.nflags = res[2]
︙
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567

3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579

3580
3581
3582
3583
3584
3585
3586
3540
3541
3542
3543
3544
3545
3546











3547


3548
3549
3550
3551
3552
3553
3554
3555
3556

3557
3558
3559
3560
3561
3562
3563
3564







-
-
-
-
-
-
-
-
-
-
-
+
-
-









-
+







					s.nflags += s.ntrem
				else
					s.nflags = s.ntrem
			}
			if (s.next && s.next.trem2)
				break
			if (s.trem2) {
				s.prev.trem2 = true;
				s.prev.nflags = --s.nflags;
				s.prev.head = ++s.head
				if (s.nflags > 0) {
					s.nflags += s.ntrem;
				} else {
					if (s.nflags <= -2) {
						s.stemless = true;
						s.prev.stemless = true
					}
					s.nflags = s.ntrem
				trem_adj(s)
				}
				s.prev.nflags = s.nflags
				break
			}

			nflags = s.nflags

			if (s.ntrem)
				nflags += s.ntrem
			if (s.type == C.REST && s.beam_end
			 && !s.beam_on) {
				s.beam_end = false;
//				s.beam_end = false;
				start_flag = true
			}
			if (start_flag
			 || nflags <= 0) {
				if (lastnote) {
					lastnote.beam_end = true;
					lastnote = null
︙
3707
3708
3709
3710
3711
3712
3713

3714
3715
3716
3717







3718
3719
3720
3721

3722
3723

3724
3725
3726
3727
3728
3729

3730
3731
3732
3733
3734
3735



3736
3737
3738
3739
3740
3741


3742
3743
3744


3745
3746
3747
3748


3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763








































3764

3765
3766
3767




















3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785

3786
3787

3788
3789
3790
3791
3792
3793
3794
3795
3796



3797
3798
3799
3800
3801
3802
3803
3685
3686
3687
3688
3689
3690
3691
3692




3693
3694
3695
3696
3697
3698
3699

3700


3701


3702






3703






3704
3705
3706
3707
3708
3709



3710
3711



3712
3713
3714
3715
3716
3717
3718
3719















3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761



3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798

3799


3800
3801
3802
3803
3804
3805
3806
3807


3808
3809
3810
3811
3812
3813
3814
3815
3816
3817







+
-
-
-
-
+
+
+
+
+
+
+
-

-
-
+
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+



-
-
-
+
+
-
-
-
+
+




+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

















-
+
-
-
+







-
-
+
+
+







		set_ottava();

	// set the clefs and adjust the pitches of all symbol
	set_clefs();
	self.set_pitch(null)
}

// get the left offsets of the first and other staff systems
/* -- return the left indentation of the staves -- */
function set_indent(first) {
	var	st, v, w, p_voice, p, i, j, font,
		nv = voice_tb.length,
// return [lsh1, lsho]
function get_lshift() {
    var	st, v, p_v, p1, po, fnt, w,
	sy = cur_sy,
	lsh1 = 0,
	lsho = 0, 
	nv = voice_tb.length
		maxw = 0

	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
	// get the max width of a voice name/subname
		if (!cur_sy.voices[v])
			continue
	function get_wx(p, wx) {
		st = cur_sy.voices[v].st
//		if (!cur_sy.st_print[st])
//			continue
		p = ((first || p_voice.new_name) && p_voice.nm) ?
			p_voice.nm : p_voice.snm
		if (!p)
	    var	w, j,
			continue
		if (!font) {
			set_font("voice");
			font = gene.curfont
		}
		i = 0
		i = 0

		p += '\n'
		while (1) {
			j = p.indexOf("\n", i)
			if (j < 0)
				w = strwh(p.slice(i))
			else
				w = strwh(p.slice(i, j))
				break
			w = strwh(p.slice(i, j))[0] + 12
			w = w[0]
			if (w > maxw)
				maxw = w
			if (w > wx)
				wx = w
			if (j < 0)
				break
			i = j + 1
		}
		return wx
	} // get_wx()
	}
	if (font)
		maxw += 4 * cwidf(' ');

	w = .5				// (width of left bar)
	for (st = 0; st <= cur_sy.nstaff; st++) {
		if (cur_sy.staves[st].flags
				& (OPEN_BRACE2 | OPEN_BRACKET2)) {
			w = 12
			break
		}
		if (cur_sy.staves[st].flags & (OPEN_BRACE | OPEN_BRACKET))
			w = 6
	}
	maxw += w

	for (v = 0; v < nv; v++) {
		p_v = voice_tb[v]
		p1 = p_v.nm
		po = p_v.snm
		if ((p1 || po) && !fnt) {
			set_font("voice")
			fnt = gene.deffont
		}
		if (p1) {
			w = get_wx(p1, lsh1)
			if (w > lsh1)
				lsh1 = w
		}
		if (po) {
			w = get_wx(po, lsho)
			if (w > lsho)
				lsho = w
		}
	}
	// add the width of the braces/brackets
	w = 0
	while (sy) {
		for (st = 0; st <= sy.nstaff; st++) {
			if (sy.staves[st].flags
					& (OPEN_BRACE2 | OPEN_BRACKET2)) {
				w = 12
				break
			}
			if (sy.staves[st].flags & (OPEN_BRACE | OPEN_BRACKET))
				w = 6
		}
		if (w == 12)
			break
		sy = sy.next
	}
	lsh1 += w
	lsho += w
	return [lsh1, lsho]
} // get_lshift()

/* -- return the left indentation of the staves -- */
	if (first)			// if %%indent
		maxw += cfmt.indent
	return maxw
function set_indent(lsh) {
    var	st, v, w, p_voice, p, i, j, font,
	vnt = 0,
	nv = voice_tb.length

	// name or subname?
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		if (!cur_sy.voices[v]
		 || !gene.st_print[p_voice.st])
			continue
		if (p_voice.new_name) {
			vnt = 2		// full name
			break
		}
		if (p_voice.snm)
			vnt = 1		// subname
	}
	gene.vnt = vnt			// voice name type for draw
	return vnt == 2 ? lsh[0] : lsh[1]
}

/* -- decide on beams and on stem directions -- */
/* this routine is called only once per tune */
function set_beams(sym) {
    var	s, t, g, beam, s_opp, n, m, mid_p, pu, pd,
	laststem = -1

	for (s = sym; s; s = s.next) {
		if (s.type != C.NOTE) {
			if (s.type != C.GRACE)
				continue
			g = s.extra
			if (g.stem == 2) {	/* opposite gstem direction */
				s_opp = s
				continue
			}
			if (!s.stem
			if (!s.stem)
			 && (s.stem = s.multi) == 0)
				s.stem = 1
				s.stem = s.multi || 1
			for (; g; g = g.next) {
				g.stem = s.stem;
				g.multi = s.multi
			}
			continue
		}

		if (!s.stem			/* if not explicitly set */
		 && (s.stem = s.multi) == 0) { /* and alone on the staff */
		if (!s.stem && s.multi)
			s.stem = s.multi
		if (!s.stem) {			// if note alone on the staff
			mid_p = s.mid / 3 + 18

			/* notes in a beam have the same stem direction */
			if (beam) {
				s.stem = laststem
			} else if (s.beam_st && !s.beam_end) {	// beam start
				beam = true;
︙
4437
4438
4439
4440
4441
4442
4443
4444

4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471


4472
4473
4474
4475
4476
4477
4478
4451
4452
4453
4454
4455
4456
4457

4458
4459
4460
4461
4462
4463
4464
4465




4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479


4480
4481
4482
4483
4484
4485
4486
4487
4488







-
+







-
-
-
-














-
-
+
+







	}
}

/* -- set the stem height -- */
/* this routine is called only once per tune */
// (possible hook)
Abc.prototype.set_stems = function() {
	var s, s2, g, slen, scale,ymn, ymx, nflags, ymin, ymax, res
	var s, s2, g, slen, scale,ymn, ymx, nflags, ymin, ymax

	for (s = tsfirst; s; s = s.ts_next) {
		if (s.type != C.NOTE) {
			if (s.type != C.GRACE)
				continue
			ymin = ymax = s.mid
			for (g = s.extra; g; g = g.next) {
				res = identify_note(s, g.dur);
				g.head = res[0];
				g.dots = res[1];
				g.nflags = res[2];
				slen = GSTEM
				if (g.nflags > 1)
					slen += 1.2 * (g.nflags - 1);
				ymn = 3 * (g.notes[0].pit - 18);
				ymx = 3 * (g.notes[g.nhd].pit - 18)
				if (s.stem >= 0) {
					g.y = ymn;
					g.ys = ymx + slen;
					ymx = Math.round(g.ys)
				} else {
					g.y = ymx;
					g.ys = ymn - slen;
					ymn = Math.round(g.ys)
				}
				ymx += 2;
				ymn -= 2
				ymx += 4
				ymn -= 4
				if (ymn < ymin)
					ymin = ymn
				else if (ymx > ymax)
					ymax = ymx;
				g.ymx = ymx;
				g.ymn = ymn
			}
︙
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614




4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625


4626
4627


4628

4629
4630
4631
4632
4633
4634
4635
4636
4637
4638




4639
4640
4641

4642
4643
4644
4645
4646
4647
4648
4649
4585
4586
4587
4588
4589
4590
4591













4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609


4610
4611
4612
4613
4614
4615
4616




4617
4618


4619
4620


4621
4622
4623
4624
4625
4626




4627
4628


4629
4630
4631
4632
4633
4634

4635

4636
4637
4638
4639
4640
4641
4642







-
-
-
-
-
-
-
-
-
-
-
-
-


















-
-
+
+
+
+



-
-
-
-


-
-
+
+
-
-
+
+

+


-
-
-
-


-
-
+
+
+
+


-
+
-







			if (s.notes[s.nhd].tie)
				ymx += 3;
			s.ymx = ymx + 4
		}
	}
}

/* -- move the symbols of an empty staff to the next one -- */
function sym_staff_move(st) {
	for (var s = tsfirst; s; s = s.ts_next) {
		if (s.nl)
			break
		if (s.st == st
		 && s.type != C.CLEF) {
			s.st++;
			s.invis = true
		}
	}
}

// generate a block symbol
var blocks = []		// array of delayed block symbols

// (possible hook)
Abc.prototype.block_gen = function(s) {
	switch (s.subtype) {
	case "leftmargin":
	case "rightmargin":
	case "pagescale":
	case "pagewidth":
	case "scale":
	case "staffwidth":
		self.set_format(s.subtype, s.param)
		break
	case "mc_start":		// multicol start
		multicol = {
			posy: posy,
			maxy: posy,
			lmarg: cfmt.leftmargin,
			rmarg: cfmt.rightmargin
			lm: cfmt.leftmargin,
			rm: cfmt.rightmargin,
			w: cfmt.pagewidth,
			sc: cfmt.scale
		}
		break
	case "mc_new":			// multicol new
		if (!multicol) {
			error(1, s, "%%multicol new without start")
			break
		}
		if (posy > multicol.maxy)
			multicol.maxy = posy
		cfmt.leftmargin = multicol.lmarg
		cfmt.rightmargin = multicol.rmarg
		cfmt.leftmargin = multicol.lm
		cfmt.rightmargin = multicol.rm
		img.chg = true
		set_page()
		cfmt.pagewidth = multicol.w
		cfmt.scale = multicol.sc
		posy = multicol.posy
		img.chg = 1 //true
		break
	case "mc_end":			// multicol end
		if (!multicol) {
			error(1, s, "%%multicol new without start")
			break
		}
		if (posy < multicol.maxy)
			posy = multicol.maxy
		cfmt.leftmargin = multicol.lmarg
		cfmt.rightmargin = multicol.rmarg
		cfmt.leftmargin = multicol.lm
		cfmt.rightmargin = multicol.rm
		cfmt.pagewidth = multicol.w
		cfmt.scale = multicol.sc
		multicol = undefined
		blk_flush()
		img.chg = true
		img.chg = 1 //true
		set_page()
		break
	case "ml":
		blk_flush()
		user.img_out(s.text)
		break
	case "newpage":
		if (!user.page_format)
︙
4670
4671
4672
4673
4674
4675
4676














4677
4678
4679
4680
4681
4682
4683
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690







+
+
+
+
+
+
+
+
+
+
+
+
+
+







		write_title(s.text, true)
		break
	case "vskip":
		vskip(s.sk);
		break
	}
}

// -- move some symbols of an empty staff to the next one --
function sym_staff_move(st) {
	for (var s = tsfirst; s; s = s.ts_next) {
		if (s.nl)
			break
		if (s.st == st
		 && s.type != C.CLEF) {
			s.st++
			if (s.type != C.TEMPO)
				s.invis = true
		}
	}
} // sym_staff_move()

/* -- define the start and end of a piece of tune -- */
/* tsnext becomes the beginning of the next line */
function set_piece() {
    var	s, last, p_voice, st, v, nv, tmp,
	non_empty = [],
	non_empty_gl = [],
︙
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4737
4738
4739
4740
4741
4742
4743




4744
4745
4746
4747
4748
4749
4750







-
-
-
-








			// ledger lines
			// index = line number
			// values = [x symbol, x start, x stop]
			p_staff.hlu = []	// above the staff
			p_staff.hld = []	// under the staff

			if (!non_empty_gl[st]) {
				p_staff.botbar = p_staff.topbar = 0
				continue
			}
			l = p_staff.stafflines.length;
			p_staff.topbar = 6 * (l - 1)

			for (i = 0; i < l - 1; i++) {
				switch (p_staff.stafflines[i]) {
				case '.':
				case '-':
︙
4756
4757
4758
4759
4760
4761
4762


4763
4764
4765
4766
4767
4768
4769
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774







+
+







					p_staff.topbar += 6
				} else {		// no line: big bar
					p_staff.botbar -= 12;
					p_staff.topbar += 12
					continue	// no helper line
				}
			}
			if (!non_empty_gl[st])
				continue

			// define the helper lines
			p_staff.hll = 17 + i * 2	// pitch of lowest note
							// without helper line
							// ('D' when standard staff)
			p_staff.hlmap = new Int8Array(new Array((l - i + 1) * 2))
			for (j = 1; i < l; i++, j += 2) {
︙
4871
4872
4873
4874
4875
4876
4877
4878

4879
4880
4881
4882
4883
4884
4885

4886
4887

4888
4889
4890
4891
4892
4893
4894
4876
4877
4878
4879
4880
4881
4882

4883

4884
4885
4886
4887
4888

4889
4890

4891
4892
4893
4894
4895
4896
4897
4898







-
+
-





-
+

-
+







	/* set the last empty staves */
	set_brace()
	sy.st_print = new Uint8Array(non_empty);

	/* define the offsets of the measure bars */
	set_top_bot()

	/* move the symbols of the empty staves to the next staff */
	// move the symbols of the empty staves to the next staff
//fixme: could be optimized (use a old->new staff array)
	for (st = 0; st < nstaff; st++) {
		if (!non_empty_gl[st])
			sym_staff_move(st)
	}

	/* let the last empty staff have a full height */
	// set a null height if the last staff is empty
	if (!non_empty_gl[nstaff])
		staff_tb[nstaff].topbar = 0;
		staff_tb[nstaff].topbar = 0

	/* initialize the music line */
	init_music_line();

	// keep the array of the staves to be printed
	gene.st_print = new Uint8Array(non_empty_gl)

︙
5101
5102
5103
5104
5105
5106
5107
5108

5109
5110
5111
5112
5113
5114
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
5128
5129

5130
5131

5132
5133
5134
5135
5136
5137







5138
5139
5140
5141
5142

5143
5144
5145
5146
5147
5148
5149
5150
5151
5152
5153





5154
5155
5156
5157
5158
5159
5160
5161
5162


5163

5164
5165
5166
5167
5168
5169
5170


5171
5172
5173
5174
5175
5176
5177
5178
5179
5180
5181
5182
5183
5184
5185
5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5105
5106
5107
5108
5109
5110
5111

5112
5113
5114
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127
5128
5129
5130
5131
5132

5133
5134

5135
5136
5137
5138
5139


5140
5141
5142
5143
5144
5145
5146
5147
5148
5149
5150

5151
5152
5153
5154
5155
5156
5157
5158
5159
5160
5161
5162
5163
5164
5165
5166
5167
5168
5169
5170
5171
5172
5173
5174
5175
5176
5177
5178

5179
5180
5181


5182
5183
5184
5185
5186
5187
5188
5189
5190
5191
5192
5193
5194
5195
5196
5197
5198
5199
5200
5201

5202
5203
5204
5205
5206
5207
5208
5209
5210
5211
5212
5213







-
+




















-
+

-
+




-
-
+
+
+
+
+
+
+




-
+











+
+
+
+
+









+
+
-
+


-
-



+
+















-












	}
	tsfirst = null			/* no more notes */
}

/* -- generate the music -- */
// (possible hook)
Abc.prototype.output_music = function() {
    var v, lwidth, indent, line_height, ts1st, tslast, p_v,
    var v, lwidth, indent, lsh, line_height, ts1st, tslast, p_v,
	nv = voice_tb.length

	set_global()
	if (nv > 1)			// if many voices
		self.set_stem_dir()	// set the stems direction in 'multi'

	for (v = 0; v < nv; v++)
		set_beams(voice_tb[v].sym);	/* decide on beams */

	self.set_stems()		// set the stem lengths

	gen_init()			// output the blocks and define the page layout
	if (!tsfirst)
		return

	set_acc_shft()			// set the horizontal offset of accidentals
	if (nv > 1) {			// if many voices
		set_rest_offset();	/* set the vertical offset of rests */
		set_overlap();		/* shift the notes on voice overlap */
	}
	set_allsymwidth();		/* set the width of all symbols */
	set_allsymwidth(1)		// set the width of all symbols

	indent = set_indent(true)
	lsh = get_lshift()

	/* if single line, adjust the page width */
	if (cfmt.singleline) {
		v = get_ck_width();
		lwidth = indent + v[0] + v[1] + get_width(tsfirst, null)[0];
		img.width = lwidth * cfmt.scale + img.lm + img.rm + 2
		lwidth = lsh[0] + v[0] + v[1] + get_width(tsfirst, null)[0]
		v = cfmt.singleline == 2	// if as wide as the page width
			? get_lwidth() : lwidth
		if (v > lwidth)
			lwidth = v
		else
			img.width = lwidth * cfmt.scale + img.lm + img.rm + 2
	} else {

	/* else, split the tune into music lines */
		lwidth = get_lwidth();
		cut_tune(lwidth, indent)
		cut_tune(lwidth, lsh)
	}

	// save symbol pointers for play
	ts1st = tsfirst
	v = nv
	while (--v >= 0)
		voice_tb[v].osym = voice_tb[v].sym

	spf_last = 0				// last spacing factor
	while (1) {				/* loop per music line */
		set_piece();
		indent = set_indent(lsh)
		if (!line_height
		 && cfmt.indent
		 && indent < cfmt.indent)
		 	indent = cfmt.indent
		self.set_sym_glue(lwidth - indent)
		if (realwidth) {
			if (indent)
				posx += indent;
			draw_sym_near();		// delayed output
			line_height = set_staff();
			draw_systems(indent);
			draw_all_sym();
			delayed_update();
//fixme: KO if at start of tune (the header is generated)
			if (output)			// if some music
			vskip(line_height)
				vskip(line_height)
			if (indent)
				posx -= indent;
			while (blocks.length)
				self.block_gen(blocks.shift())
		}

		blk_flush()
		while (blocks.length)
			self.block_gen(blocks.shift())
		if (tslast)
			tslast.ts_next.ts_prev = tslast
		if (!tsnext)
			break
		tsnext.ts_prev.ts_next =		// (restore for play)
			tsfirst = tsnext

		// next line
		gen_init()
		if (!tsfirst)
			break
		tslast = tsfirst.ts_prev
		tsfirst.ts_prev = null;
		set_sym_line();
		lwidth = get_lwidth()	// the image size may have changed
		indent = set_indent()
	}

	// restore for play
	tsfirst = ts1st
	v = nv
	while (--v >= 0) {
		p_v = voice_tb[v]
		if (p_v.sym && p_v.s_prev)
			p_v.sym.prev = p_v.s_prev
		p_v.sym = p_v.osym
	}
}
Changes to core/parse.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - parse.js - ABC parse
//
// Copyright (C) 2014-2021 Jean-Francois Moine
// Copyright (C) 2014-2023 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
151
152
153
154
155
156
157



































































158
159
160
161
162
163
164
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







				return
			}
			pit[i] = abc2svg.pab40(note.pit, note.acc)
		}
	}
	return pit[1] - pit[0]
}

// transpose a note for the score
// Return the transposed real accidental
function nt_trans(nt,
		  a) {			// real accidental
    var	ak, an, d, b40, n

	if (typeof a == "object") {		// if microtonal accidental
		n = a[0]			// numerator
		d = a[1]			// denominator
		a = n > 0 ? 1 : -1		// base accidental for transpose
	}

	b40 = abc2svg.pab40(nt.pit, a)
		+ curvoice.tr_sco		// base-40 transposition

	nt.pit = abc2svg.b40p(b40)		// new pitch
	an = abc2svg.b40a(b40)			// new accidental

	if (!d) {				// if not a microtonal accidental
		if (an == -3)			// if triple sharp/flat
			return an
		a = an
		if (nt.acc) {			// if old accidental
			if (!a)
				a = 3		// required natural
		} else {
			if (!curvoice.ckey.k_none) // if normal key
				a = 0		// no accidental
		}
		nt.acc = a
		return an
	}

	// set the microtonal accidental after transposition
	switch (a) {
	case -2:
		if (n > 0)
			n -= d * 2
		else
			n -= d
		break
	case -1:
		if (n > 0)
			n -= d
		break
	case 0:
	case 3:
		if (n > 0)
			n -= d
		else
			n += d
		break
	case 1:
		if (n < 0)
			n += d
		break
	case 2:
		if (n < 0)
			n += d * 2
		else
			n += d
		break
	}
	nt.acc = [n, d]
	return an
} // nt_trans()

// set the linebreak character
function set_linebreak(param) {
	var i, item

	for (i = 0; i < 128; i++) {
		if (char_tb[i] == "\n")
︙
275
276
277
278
279
280
281
282


283
284
285
286
287
288
289
342
343
344
345
346
347
348

349
350
351
352
353
354
355
356
357







-
+
+







	sym_link(s)
	return s
}

// set the voice parameters
// (possible hook)
Abc.prototype.set_vp = function(a) {
    var	s, item, pos, val, clefpit, tr_p
    var	s, item, pos, val, clefpit,
	tr_p = 0

	while (1) {
		item = a.shift()
		if (!item)
			break
		if (item.slice(-1) == '='
		 && !a.length) {
︙
331
332
333
334
335
336
337
338


339
340
341
342
343
344


345
346
347
348
349
350


351
352
353
354
355
356
357
399
400
401
402
403
404
405

406
407
408
409
410
411
412

413
414
415
416
417
418


419
420
421
422
423
424
425
426
427







-
+
+





-
+
+




-
-
+
+







			// (instrument=M == instrument=M/M)
			item = a.shift()
			val = item.indexOf('/')
			if (val < 0) {
				val = get_interval('c' + item)
				if (val == undefined)
					break
				curvoice.sndtran = val
				curvoice.sound = val
				tr_p |= 2
				val = 0
			} else {
				val = get_interval('c' + item.slice(val + 1))
				if (val == undefined)
					break
				curvoice.sndtran = val
				curvoice.sound = val
				tr_p |= 2
				val = get_interval(item.replace('/', ''))
				if (val == undefined)
					break
			}
			curvoice.transp = cfmt.sound ? curvoice.sndtran : val
			tr_p = 1
			curvoice.score = cfmt.sound ? curvoice.sound : val
			tr_p |= 1
			break
		case "map=":			// %%voicemap
			curvoice.map = a.shift()
			break
		case "name=":
		case "nm=":
			curvoice.nm = a.shift()
︙
396
397
398
399
400
401
402
403
404


405
406
407
408
409
410
411
412
413
414
415


416
417
418
419
420
421
422
423
424
425
426
427
428
429

430
431
432


433
434
435
436
437
438
439
466
467
468
469
470
471
472


473
474
475
476
477
478
479
480
481
482
483


484
485
486
487
488
489
490
491
492
493
494
495
496
497
498

499
500


501
502
503
504
505
506
507
508
509







-
-
+
+









-
-
+
+













-
+

-
-
+
+







			// score=MN
			// (score=M == score=Mc)
			item = a.shift()
			if (cfmt.sound)
				break
			val = get_interval(item, true)
			if (val != undefined) {
				curvoice.transp = val
				tr_p = 1
				curvoice.score = val
				tr_p |= 1
			}
			break
		case "shift=":
			if (cfmt.nedo) {
				syntax(1, errs.notransp)
				break
			}
			val = get_interval(a.shift())
			if (val != undefined) {
				curvoice.shift = curvoice.sndsh = val
				tr_p = 1
				curvoice.shift = val
				tr_p = 3
			}
			break
		case "sound=":
			if (cfmt.nedo) {
				syntax(1, errs.notransp)
				break
			}
// concert-score display: apply sound=
// sounding-score display: apply sound= only if M != c/C
// sound: apply sound=
			val = get_interval(a.shift())
			if (val == undefined)
				break
			curvoice.sndtran = val
			curvoice.sound = val
			if (cfmt.sound)
				curvoice.transp = val
			tr_p = 1
				curvoice.score = val
			tr_p |= 2
			break
		case "subname=":
		case "sname=":
		case "snm=":
			curvoice.snm = a.shift()
			if (curvoice.snm[0] == '"')
				curvoice.snm = curvoice.snm.slice(1, -1);
︙
466
467
468
469
470
471
472
473

474
475
476


477
478
479
480
481
482
483
536
537
538
539
540
541
542

543
544


545
546
547
548
549
550
551
552
553







-
+

-
-
+
+







				syntax(1, errs.notransp)
				break
			}
			val = get_transp(a.shift())
			if (val == undefined) {
				syntax(1, errs.bad_transp)
			} else {
				curvoice.sndtran = val
				curvoice.sound = val
				if (cfmt.sound)
					curvoice.transp = val
				tr_p = 1
					curvoice.score = val
				tr_p = 2
			}
			break
		default:
			switch (item.slice(0, 4)) {
			case "treb":
			case "bass":
			case "alto":
︙
506
507
508
509
510
511
512


513
514
















515
516
517
518
519
520
521
522
523
524
525




526
527
528
529
530
531
532
576
577
578
579
580
581
582
583
584


585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607




608
609
610
611
612
613
614
615
616
617
618







+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
-
-
-
+
+
+
+







		s = new_clef(s)
		if (s) {
			if (clefpit)
				s.clefpit = clefpit
			get_clef(s)
		}
	}

	// if transposition
	if (tr_p)
		set_transp()
	if (tr_p) {
		curvoice.tr_p = tr_p			// keep this key signature
		tr_p = (curvoice.score | 0)
			+ (curvoice.shift | 0)
			+ (cfmt.transp | 0)
		if (tr_p)
			curvoice.tr_sco = tr_p		// b40 interval
		else if (curvoice.tr_sco)
			curvoice.tr_sco = 0
		tr_p = (curvoice.sound | 0) + (curvoice.shift | 0)
		if (tr_p)
			curvoice.tr_snd = abc2svg.b40m(tr_p + 122) - 36
							// semi-tone interval
		else if (curvoice.tr_snd)
			curvoice.tr_snd = 0
	}
} // set_vp()

// set the K: / V: parameters
function set_kv_parm(a) {	// array of items
	if (!curvoice.init) {	// add the global parameters if not done yet
		curvoice.init = true
		if (info.V) {
			if (info.V['*'])
				a = info.V['*'].concat(a)
			if (info.V[curvoice.id])
				a = info.V[curvoice.id].concat(a)
			if (info.V[curvoice.id])
				a = info.V[curvoice.id].concat(a)
			if (info.V['*'])
				a = info.V['*'].concat(a)
		}
	}
	if (a.length)
		self.set_vp(a)
} // set_kv_parm()

// memorize the K:/V: parameters
︙
541
542
543
544
545
546
547
548

549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
627
628
629
630
631
632
633

634
635
636
637
638
639
640
641










































642
643
644
645
646
647
648







-
+







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







	else
		info.V[vid] = a
}

// K: key signature
// return the key and the voice/clef parameters
function new_key(param) {
    var	i, clef, key_end, c, tmp, exp,
    var	i, key_end, c, tmp, note,
	sf = "FCGDAEB".indexOf(param[0]) - 1,
	mode = 0,
	s = {
		type: C.KEY,
		dur: 0
	}

	// set the accidentals when K: with modified accidentals
	function set_k_acc(s, sf) {
	    var i, j, n, nacc, p_acc,
		accs = [],
		pits = []

		if (sf > 0) {
			for (nacc = 0; nacc < sf; nacc++) {
				accs[nacc] = 1;			// sharp
				pits[nacc] = [26, 23, 27, 24, 21, 25, 22][nacc]
			}
		} else {
			for (nacc = 0; nacc < -sf; nacc++) {
				accs[nacc] = -1;		// flat
				pits[nacc] = [22, 25, 21, 24, 20, 23, 26][nacc]
			}
		}
		n = s.k_a_acc.length
		for (i = 0; i < n; i++) {
			p_acc = s.k_a_acc[i]
			for (j = 0; j < nacc; j++) {
				if (pits[j] == p_acc.pit) {
					accs[j] = p_acc.acc
					break
				}
			}
			if (j == nacc) {
				accs[j] = p_acc.acc;
				pits[j] = p_acc.pit
				nacc++
			}
		}
		for (i = 0; i < nacc; i++) {
			p_acc = s.k_a_acc[i]
			if (!p_acc)
				p_acc = s.k_a_acc[i] = {}
			p_acc.acc = accs[i];
			p_acc.pit = pits[i]
		}
	} // set_k_acc()

	// code of new_key()
	set_ref(s);

	// tonic
	i = 1
    if (sf < -1) {
	switch (param[0]) {
	case 'H':				// bagpipe
︙
695
696
697
698
699
700
701
702

703
704
705
706
707
708
709
710

711
712
713
714
715
716
717
718
719
720
721

722
723
724
725
726







727
728
729



730
731

732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
739
740
741
742
743
744
745

746
747
748
749
750
751
752
753

754
755
756
757
758
759
760
761


762

763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782

783
784
785
786
787
788





789
790
791
792
793
794
795







-
+







-
+







-
-

-
+





+
+
+
+
+
+
+



+
+
+

-
+





-
-
-
-
-







			param = param.replace(/\w+\s*/, '')

		// [exp] accidentals
		if (param.indexOf("exp ") == 0) {
			param = param.replace(/\w+\s*/, '')
			if (!param)
				syntax(1, "No accidental after 'exp'");
			exp = true
			s.exp = 1 //true
		}
		c = param[0]
		if (c == '^' || c == '_' || c == '=') {
			s.k_a_acc = [];
			tmp = new scanBuf;
			tmp.buffer = param
			do {
				var note = parse_acc_pit(tmp)
				note = parse_acc_pit(tmp)
				if (!note)
					break
				s.k_a_acc.push(note);
				c = param[tmp.index]
				while (c == ' ')
					c = param[++tmp.index]
			} while (c == '^' || c == '_' || c == '=');
			if (!exp)
				set_k_acc(s, sf)
			param = param.slice(tmp.index)
		} else if (exp && param.indexOf("none") == 0) {
		} else if (s.exp && param.indexOf("none") == 0) {
			sf = 0
			param = param.replace(/\w+\s*/, '')
		}
	}

	if (sf < -7 || sf > 7) {
		syntax(1, "Key with double sharps/flats")
		if (sf > 7)
			sf -= 12
		else
			sf += 12
	}
	s.k_sf = sf;

	// set the map of the notes with accidentals
	s.k_map = s.k_bagpipe && !sf
		? abc2svg.keys[9]		// implicit F# and C#
		: abc2svg.keys[sf + 7]
	if (s.k_a_acc) {
		s.k_map = []
		s.k_map = new Int8Array(s.k_map)
		i = s.k_a_acc.length
		while (--i >= 0) {
			note = s.k_a_acc[i]
			s.k_map[(note.pit + 19) % 7] = note.acc
		}
	} else {
		s.k_map = s.k_bagpipe && !sf
			? abc2svg.keys[9]		// implicit F# and C#
			: abc2svg.keys[sf + 7]

	}
	s.k_mode = mode

	// key note as base-40
	s.k_b40 = [1,24,7,30,13,36,19, 2 ,25,8,31,14,37,20,3][sf + 7]

	return [s, info_split(param)]
︙
887
888
889
890
891
892
893




894
895
896
897
898
899
900
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951







+
+
+
+







	if (p[i] == '=') {
		val = p.substring(++i).match(/^(\d+)\/(\d+)$/)
		if (!val) {
			syntax(1, "Bad duration '$1' in M:", p.substring(i))
			return
		}
		wmeasure = C.BLEN * val[1] / val[2]
	}
	if (!wmeasure) {
		syntax(1, errs.bad_val, 'M:')
		return
	}
	s.wmeasure = wmeasure

	if (cfmt.writefields.indexOf('M') < 0)
		s.a_meter = []

	if (parse.state != 3) {
︙
1009
1010
1011
1012
1013
1014
1015
1016


1017
1018
1019
1020

1021

1022
1023
1024
1025
1026










1027
1028
1029
1030
1031

1032
1033

1034
1035
1036










1037
1038
1039
1040
1041
1042
1043
1060
1061
1062
1063
1064
1065
1066

1067
1068
1069
1070
1071
1072
1073

1074



1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090

1091
1092
1093
1094



1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111







-
+
+




+
-
+
-
-
-


+
+
+
+
+
+
+
+
+
+




-
+


+
-
-
-
+
+
+
+
+
+
+
+
+
+







			if (!s.tempo || isNaN(s.tempo)) {
				syntax(1, "Bad tempo value")
				return
			}
		}
	}

	if (parse.state < 2) {			// if in tune header
	if (parse.state < 2			// if in tune header
	 || (!curvoice.time && !glovar.tempo)) {
		info.Q = txt
		glovar.tempo = s
		return
	}

	if (!curvoice.time)
    var	tim = curvoice.time
		glovar.tempo = s
	else
		sym_link(s)
	if (!glovar.tempo)
		syntax(0, "No previous tempo")
	if (!parse.ctrl)
		parse.ctrl = {}
	if (!parse.ctrl[tim])
		parse.ctrl[tim] = {}
	parse.ctrl[tim].tempo = s
	s.v = par_sy.top_voice
	s.p_v = voice_tb[s.v]
	s.fmt = cfmt
	s.st = s.p_v.st
	s.time = tim
}

// treat the information fields which may embedded
function do_info(info_type, text) {
	var s, d1, d2, a, vid
    var	s, d1, d2, a, vid, tim, v, p_v

	// skip this line if the current voice is ignored
	// but keep the time related definitions
	if (curvoice && curvoice.ignore
	 && info_type != 'V')
		return
	if (curvoice && curvoice.ignore) {
		switch (info_type) {
		default:
			return
		case 'P':
		case 'Q':
		case 'V':
			break
		}
	}

	switch (info_type) {

	// info fields in any state
	case 'I':
		self.do_pscom(text)
		break
︙
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088



1089
1090
1091
1092

1093
1094
1095
1096
1097









1098
1099
1100
1101
1102
1103
1104
1147
1148
1149
1150
1151
1152
1153



1154
1155
1156

1157
1158

1159
1160
1161
1162
1163

1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179







-
-
-
+
+
+
-


-
+




-
+
+
+
+
+
+
+
+
+







	case 'P':
		if (!parse.state)
			break
		if (parse.state == 1) {
			info.P = text
			break
		}
		if (!parse.part)
			parse.part = {}
		if (parse.part[curvoice.time])	// P: already defined

		// memorize the part in the control
		tim = curvoice.time
			break
		s = {
			text: text,
			time: curvoice.time
			time: tim
		}
		set_ref(s)
		if (cfmt.writefields.indexOf(info_type) < 0)
			s.invis = true
		parse.part[curvoice.time] = s
		if (!parse.ctrl)
			parse.ctrl = {}
		if (!parse.ctrl[tim])
			parse.ctrl[tim] = {}
		parse.ctrl[tim].part = s
		s.v = par_sy.top_voice
		s.p_v = voice_tb[s.v]
		s.fmt = cfmt
		s.st = s.p_v.st
		break
	case 'Q':
		if (!parse.state)
			break
		new_tempo(text)
		break
	case 'V':
︙
1317
1318
1319
1320
1321
1322
1323
1324
1325

1326
1327
1328
1329
1330
1331
1332
1392
1393
1394
1395
1396
1397
1398


1399
1400
1401
1402
1403
1404
1405
1406







-
-
+







			delete s.text
		}
		curvoice.tie_s_rep = null	// no tie anymore on new variant
	}

	// handle the accidentals (ties and repeat)
	if (s.text) {
		s.rbstop = 2;
		s.rbstart = 2
		s.rbstart = s.rbstop = 2
		if (s.text[0] == '1') {
			curvoice.tie_s_rep = curvoice.tie_s
			if (curvoice.acc_tie)
				curvoice.acc_tie_rep = curvoice.acc_tie.slice()
			else if (curvoice.acc_tie_rep)
				curvoice.acc_tie_rep = null
		} else {
︙
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1474
1475
1476
1477
1478
1479
1480

1481
1482
1483
1484
1485
1486
1487







-







			}
		}
	}

	/* set some flags */
	switch (bar_type) {
	case "[":
		s.rbstop = 2
	case "[]":
	case "[|]":
		s.invis = true;
		bar_type = s.rbstart ? "[" : "[]"
		break
	case ":|:":
	case ":||:":
︙
1601
1602
1603
1604
1605
1606
1607
1608

1609
1610
1611
1612
1613
1614
1615
1674
1675
1676
1677
1678
1679
1680

1681
1682
1683
1684
1685
1686
1687
1688







-
+







	return a_vf
}

// split an info string
function info_split(text) {
	if (!text)
		return []
    var	a = text.match(/[^\s"=]+=?|"[^"]+"/g)	// "
    var	a = text.match(/[^\s"=]+=?|"[^"]*"/g)	// "
	if (!a) {
//fixme: bad error text
		syntax(1, "Unterminated string")
		return []
	}
	return a
}
︙
1626
1627
1628
1629
1630
1631
1632
1633

1634
1635
1636
1637
1638
1639
1640
1699
1700
1701
1702
1703
1704
1705

1706
1707
1708
1709
1710
1711
1712
1713







-
+







	if (!res[0])
		return [1, 1];
	num = res[1] || 1;
	den = res[3] || 1
	if (!res[3])
		den *= 1 << res[2].length;
	line.index = reg_dur.lastIndex
	return [num, den]
	return [+num, +den]
}

// parse the note accidental and pitch
function parse_acc_pit(line) {
    var	note, acc, pit, d, nd,
	c = line.char()

︙
1784
1785
1786
1787
1788
1789
1790
1791
1792


1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809










1810
1811
1812
1813
1814
1815
1816
1817
1818



1819
1820
1821
1822
1823
1824
1825



1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839







1840
1841
1842



1843
1844
1845


1846
1847
1848
1849










1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863


1864
1865
1866
1867
1868
1869



1870
1871








1872
1873
1874
1875
1876
1877
1878
1879
1880
1881

1882







1883
1884
1885







1886
1887
1888






1889







1890
1891
1892
1893
1894
1895
1896

1897

1898
1899
1900
1901


1902
1903
1904
1905
1906

1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923







-
-
+
+




-
-
-


-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-

-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-

-
-
-
-
-
-
-
+
+
+
+
+
+
+
-

-
+
+
+

-
-
+
+



-
+
+
+
+
+
+
+
+
+
+







		line.index++
		return ty + C.SL_BELOW
	}
	return ty + C.SL_AUTO
}

// on end of slur, create the slur
function slur_add(enote, e_is_note) {
    var	i, s, sl, snote, s_is_note
function slur_add(s, nt) {		// nt = note if slur ending on note
    var	i, s2, sl

	// go back and find the last start of slur
	for (i = curvoice.sls.length; --i >= 0; ) {
		sl = curvoice.sls[i]
		snote = sl.note
		s_is_note = sl.is_note
		delete sl.is_note

		// the slur must not start and stop on a same symbol
		if (snote.s != enote.s) {
			sl.note = enote
			if (e_is_note)
				sl.is_note = e_is_note
			s = s_is_note ? snote : snote.s
			if (!s.sls)
				s.sls = [];
			s.sls.push(sl)
		if (sl.ss == s)
			continue
		curvoice.sls.splice(i, 1)
		sl.se = s			// ending symbol
		if (nt)
			sl.nte = nt
		s2 = sl.ss			// start of slur
		if (!s2.sls)
			s2.sls = []
		s2.sls.push(sl)
			curvoice.sls.splice(i, 1)

			// set a flag on the start symbol if slur from a note
			if (s_is_note)
				snote.s.sl1 = true

			// set a flag if the slur starts on a grace note
			if (sl.grace)
				sl.grace.sl1 = true
		// set a flag if the slur starts on a grace note
		if (sl.grace)
			sl.grace.sl1 = true

			// set a flag if the slur ends on a grace note
			if (enote.s.grace)
				enote.s.sl2 = true
			return
		}
	}
		return
	}


	if (enote.grace) {
		error(1, enote.s, errs.bad_slur_end)
		return
	}

	// the lack of a starting slur may be due to a repeat
	for (s = enote.s.prev; s; s = s.prev) {
		if (s.type == C.BAR
		 && s.bar_type[0] == ':'
		 && s.text) {
			if (!s.sls)
				s.sls = [];
			s.sls.push({
	for (s2 = s.prev; s2; s2 = s2.prev) {
		if (s2.type == C.BAR
		 && s2.bar_type[0] == ':'
		 && s2.text) {
			if (!s2.sls)
				s2.sls = [];
			s2.sls.push({
				note: enote,
//fixme: should go back to the bar "|1" and find the slur type...
				ty: C.SL_AUTO
				ty: C.SL_AUTO,
				ss: s2,
				se: s
			})
			if (e_is_note)
				s.sls[s.sls.length - 1].is_note = e_is_note
			if (nt)
				s2.sls[s2.sls.length - 1].nte = nt
			return
		}
	}
	syntax(1, "End of slur without start")
//	syntax(1, "End of slur without start")
	if (!s.sls)
		s.sls = [];
	s.sls.push({
		ty: C.SL_AUTO,
		se: s,
		loc: 'i'			// no slur start
	})
	if (nt)
		s.sls[s.sls.length - 1].nte = nt
}

// convert a diatonic pitch and accidental to a MIDI pitch with cents
function pit2mid(pit, acc) {
    var	p = [0, 2, 4, 5, 7, 9, 11][pit % 7],	// chromatic pitch
	o = ((pit / 7) | 0) * 12,		// octave
	p0, p1, s, b40
︙
1920
1921
1922
1923
1924
1925
1926

1927
1928
1929
1930
1931
1932
1933
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001







+







				if (!not1.tie_ty)
					continue
				if (not1.midi == mid
				 && (!se
				  || !not1.tie_e)) {	// (if unison)
					not2.tie_s = not1
					not2.s = s
					delete not2.acc	// (if transposing)
					if (se) {
						not1.tie_e = not2
						not1.s = tie_s
					}
					nt++
					break
				}
︙
1956
1957
1958
1959
1960
1961
1962

1963
1964
1965
1966
1967
1968
1969
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038







+







		s.ti2 = true
} // do_ties()

// (possible hook)
Abc.prototype.new_note = function(grace, sls) {
    var	note, s, in_chord, c, dcn, type, tie_s, acc_tie,
	i, n, s2, nd, res, num, dur, apit, div, ty,
	dpit = 0,
	sl1 = [],
	line = parse.line,
	a_dcn_sav = a_dcn		// save parsed decoration names

	a_dcn = []
	parse.stemless = false;
	s = {
︙
1998
1999
2000
2001
2002
2003
2004




2005
2006
2007
2008
2009
2010
2011
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084







+
+
+
+







	switch (c) {
	case 'X':
		s.invis = true
	case 'Z':
		s.type = C.MREST;
		c = line.next_char()
		s.nmes = (c > '0' && c <= '9') ? line.get_int() : 1;
		if (curvoice.wmeasure == 1) {
			error(1, s, "multi-measure rest, but no measure!")
			return
		}
		s.dur = curvoice.wmeasure * s.nmes

		// ignore if in second voice
		if (curvoice.second) {
			delete curvoice.eoln	// ignore the end of line
			curvoice.time += s.dur
			return //null
︙
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110

2111
2112
2113
2114
2115
2116
2117
2118





2119
2120
2121
2122
2123
2124










2125
2126
2127
2128
2129

2130

2131
2132











2133
2134
2135























2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146



2147
2148
2149
2150
2151


2152

2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163

2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178




2179
2180
2181
2182
2183
2184
2185
2173
2174
2175
2176
2177
2178
2179

2180
2181
2182
2183
2184
2185






2186
2187
2188
2189
2190


2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210

2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224



2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255



2256
2257
2258
2259
2260

2261

2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274


2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287



2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298







-



+


-
-
-
-
-
-
+
+
+
+
+
-
-




+
+
+
+
+
+
+
+
+
+





+
-
+


+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








-
-
-
+
+
+


-

-
+
+

+









-
-
+












-
-
-
+
+
+
+







					s.grace ? C.BLEN / 4 :
					curvoice.ulen < 0 ?
						C.BLEN :
						curvoice.ulen)
			if (!note)
				return //null

			// transpose
			if (curvoice.octave)
				note.pit += curvoice.octave * 7

			// get the real accidental
			apit = note.pit + 19		// pitch from C-1
			i = note.acc

			// get the explicit or implicit accidental
			if (i) {
				curvoice.acc[apit] = i
			} else {
				i = curvoice.acc[apit]
			if (!i) {
				if (cfmt["propagate-accidentals"][0] == 'p')
					i = curvoice.acc[apit % 7]
				else
					i = curvoice.acc[apit]
				if (!i && acc_tie)
					i = acc_tie[apit]
				if (!i)
					i = curvoice.ckey.k_map[apit % 7] || 0
			}

			if (i) {
				if (cfmt["propagate-accidentals"][0] == 'p')
					curvoice.acc[apit % 7] = i
				else if (cfmt["propagate-accidentals"][0] != 'n')
					curvoice.acc[apit] = i
			}

			if (acc_tie && acc_tie[apit])
				i = acc_tie[apit]	// tied note

			// map
			if (curvoice.map
			 && maps[curvoice.map])
				set_map(note, i)

			// set the MIDI pitch
			if (!note.midi)			// if not map play
			if (!note.midi)		// if not map play
				note.midi = pit2mid(apit, i)

			// transpose
			if (curvoice.tr_sco) {
				i = nt_trans(note, i)
				if (i == -3) {		// if triple sharp/flat
					error(1, s, "triple sharp/flat")
					i = note.acc > 0 ? 1 : -1
					note.pit += i
					note.acc = i
				}
				dpit = note.pit + 19 - apit
			}
			if (curvoice.ckey.k_sndtran)
				note.midi += abc2svg.b40m(curvoice.ckey.k_sndtran +
						122) - 36
			if (curvoice.tr_snd)
				note.midi += curvoice.tr_snd

//fixme: does not work if transposition
			if (i) {
				switch (cfmt["writeout-accidentals"][1]) {
				case 'd':			// added
					s2 = curvoice.ckey
					if (!s2.k_a_acc)
						break
					for (n = 0; n < s2.k_a_acc.length; n++) {
						if ((s2.k_a_acc[n].pit - note.pit)
								% 7 == 0) {
							note.acc = i
							break
						}
					}
					break
				case 'l':			// all
					note.acc = i
					break
				}
			}

			// starting slurs
			if (sl1.length) {
				while (1) {
					i = sl1.shift()
					if (!i)
						break
					curvoice.sls.push({
						is_note: true,
						note: note,
						ty: i
						ty: i,
						ss: s,
						nts: note	// starting note
					})
				}
				note.s = s;		// link the note to the chord
			}
			if (a_dcn.length)
			if (a_dcn.length) {
				s.time = curvoice.time	// (needed for !tie)!
				dh_cnv(s, note)
			}
			s.notes.push(note)
			if (!in_chord)
				break

			// in chord: get the ending slurs and the ties
			c = line.char()
			while (1) {
				switch (c) {
				case ')':
					note.s = s
					slur_add(note, true)
					slur_add(s, note)
					c = line.next_char()
					continue
				case '-':
					note.tie_ty = parse_vpos()
					note.s = s
					curvoice.tie_s = s
					s.ti1 = true
					if (curvoice.acc[apit]
					 || (acc_tie
					  && acc_tie[apit])) {
						if (!curvoice.acc_tie)
							curvoice.acc_tie = []
						curvoice.acc_tie[apit] =
							curvoice.acc[apit] ||
								acc_tie[apit]
						i = curvoice.acc[apit]
						if (acc_tie && acc_tie[apit])
							i = acc_tie[apit]
						curvoice.acc_tie[apit] = i
					}
					c = line.char()
					continue
				case '.':
					c = line.next_char()
					switch (c) {
					case '-':
︙
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218



2219
2220
2221
2222
2223
2224
2225
2226
2227
2228

2229
2230
2231
2232
2233
2234
2235
2321
2322
2323
2324
2325
2326
2327

2328


2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349







-

-
-
+
+
+










+








		// handle the starting slurs
		if (sls.length) {
			while (1) {
				i = sls.shift()
				if (!i)
					break
				s.notes[0].s = s
				curvoice.sls.push({
					note: s.notes[0],
					ty: i
					ty: i,
					ss: s
					// no starting note
				})
				if (grace)
					curvoice.sls[curvoice.sls.length - 1].grace =
										grace
			}
		}

		// the duration of the chord is the duration of the 1st note
		s.dur_orig = s.notes[0].dur;
		s.dur = s.notes[0].dur * curvoice.dur_fact
		break
	}
	if (s.grace && s.type != C.NOTE) {
		syntax(1, errs.bad_grace)
		return //null
	}

	if (s.notes) {				// if note or rest
︙
2304
2305
2306
2307
2308
2309
2310

2311
2312
2313
2314
2315
2316
2317
2318




2319
2320
2321
2322
2323
2324
2325
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430



2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441







+





-
-
-
+
+
+
+







					s.notes[i].s = s
				}
				curvoice.tie_s = grace || s
				curvoice.tie_s.ti1 = true
				for (i = 0; i <= s.nhd; i++) {
					note = s.notes[i]
					apit = note.pit + 19	// pitch from C-1
						- dpit		// (if transposition)
					if (curvoice.acc[apit]
					 || (acc_tie
					  && acc_tie[apit])) {
						if (!curvoice.acc_tie)
							curvoice.acc_tie = []
						curvoice.acc_tie[apit] =
							curvoice.acc[apit] ||
								acc_tie[apit]
						n = curvoice.acc[apit]
						if (acc_tie && acc_tie[apit])
							n = acc_tie[apit]
						curvoice.acc_tie[apit] = n
					}
				}
				c = line.char()
				continue
			}
			break
		}
︙
2353
2354
2355
2356
2357
2358
2359

2360



2361
2362

2363
2364
2365
2366
2367
2368


2369


2370
2371
2372
2373
2374
2375
2376
2469
2470
2471
2472
2473
2474
2475
2476

2477
2478
2479
2480

2481
2482
2483
2484
2485
2486
2487
2488
2489

2490
2491
2492
2493
2494
2495
2496
2497
2498







+
-
+
+
+

-
+






+
+
-
+
+







		s.stemless = true
	s.iend = parse.bol + line.index
	return s
}

// adjust the duration of the elements in a tuplet
function tp_adj(s, fact) {
    var	d,
    var	tim = s.time
	tim = s.time,
	to = curvoice.time - tim,	// previous delta time
	tt = to * fact			// new delta time

	curvoice.time = tim + (curvoice.time - tim) * fact
	curvoice.time = tim + tt
	while (1) {
//fixme: tuplets in grace notes?
		s.in_tuplet = true
		if (!s.grace) {
			s.time = tim
			if (s.dur) {
				d = Math.round(s.dur * tt / to)	// new duration
				to -= s.dur		// old remaining time
				s.dur *= fact
				s.dur = d
				tt -= s.dur		// new remaining time
				tim += s.dur
			}
		}
		if (!s.next) {
			if (s.tpe)
				s.tpe++
			else
︙
2387
2388
2389
2390
2391
2392
2393

2394
2395
2396
2397
2398
2399
2400
2401
2402
2509
2510
2511
2512
2513
2514
2515
2516
2517

2518
2519
2520
2521
2522
2523
2524







+

-







	line = parse.line,
	i = line.index,		// in case no deco end
	dcn = ""

	while (1) {
		c = line.next_char()
		if (!c) {
			line.index = i
			syntax(1, "No end of decoration")
			line.index = i;
			return
		}
		if (c == '!')
			break
		dcn += c
	}
	if (ottava[dcn] != undefined) {
︙
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2699
2700
2701
2702
2703
2704
2705













2706
2707
2708
2709
2710
2711
2712







-
-
-
-
-
-
-
-
-
-
-
-
-







	    var	c, idx, type, k, s, dcn, i, n, text, note

		while (1) {
			c = line.char()
			if (!c)
				break

			// skip definitions if the current voice is ignored
			if (curvoice.ignore) {
				while (1) {
					if (c == '['
					 && line.buffer[line.index + 1] == 'V'
					 && line.buffer[line.index + 2] == ':')
						break		// [V:nn] found
					c = line.next_char()
					if (!c)
						return
				}
			}

			// check if start of a macro
			if (!in_mac && maci[c]) {
				n = undefined
				for (k in mac) {
					if (!mac.hasOwnProperty(k)
					 || k[0] != c)
						continue
︙
2658
2659
2660
2661
2662
2663
2664
2665

2666
2667

2668
2669
2670
2671
2672
2673
2674
2767
2768
2769
2770
2771
2772
2773

2774

2775
2776
2777
2778
2779
2780
2781
2782
2783







-
+
-

+







				if (c > '0' && c <= '9') {	// tuplet
					if (grace) {
						syntax(1, errs.bad_grace)
						break
					}
				    var	pplet = line.get_int(),
					qplet = qplet_tb[pplet],
					rplet = pplet,
					rplet = pplet
					c = line.char()

					c = line.char()
					if (c == ':') {
						c = line.next_char()
						if (c > '0' && c <= '9') {
							qplet = line.get_int();
							c = line.char()
						}
						if (c == ':') {
︙
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710

2711
2712
2713
2714
2715
2716
2717
2792
2793
2794
2795
2796
2797
2798















2799
2800
2801
2802
2803

2804
2805
2806
2807
2808
2809
2810
2811







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-





-
+







						}
					}
					if (qplet == 0 || qplet == undefined)
						qplet = (curvoice.wmeasure % 9) == 0 ?
									3 : 2;
					if (tpn < 0)
						tpn = tp.length	// new tuplet
					c = cfmt.tuplets
					if (curvoice.pos.tup) {
						c = Object.create(c)
						switch (curvoice.pos.tup & 0x07) {
						case C.SL_ABOVE:
							c[3] = 1
							break
						case C.SL_BELOW:
							c[3] = 2
							break
						case C.SL_HIDDEN:
							c[2] = 1
							break
						}
					}
					tp.push({
						p: pplet,
						q: qplet,
						r: rplet,
						ro: rplet,
						f: c
						f: curvoice.tup || cfmt.tuplets
					})
					continue
				}
				if (c == '&') {		// voice overlay start
					if (grace) {
						syntax(1, errs.bad_grace)
						break
︙
2745
2746
2747
2748
2749
2750
2751
2752
2753

2754
2755
2756
2757
2758
2759
2760
2839
2840
2841
2842
2843
2844
2845


2846
2847
2848
2849
2850
2851
2852
2853







-
-
+







						break
					}
				}
				if (!s) {
					syntax(1, errs.bad_char, c)
					break
				}
				s.notes[0].s = s
				slur_add(s.notes[0])
				slur_add(s)
				break
			case '!':			// start of decoration
				if (type.length > 1)	// decoration letter
					a_dcn.push(type.slice(1, -1))
				else
					get_deco()	// (line -> a_dcn)
				break
︙
2893
2894
2895
2896
2897
2898
2899




2900
2901
2902
2903
2904
2905
2906
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003







+
+
+
+







				if (grace) {
					syntax(1, errs.bar_grace)
					break
				}
				new_bar()
				continue
			case '}':
				if (curvoice.ignore) {
					grace = null
					break
				}
				s = curvoice.last_note
				if (!grace || !s) {
					syntax(1, errs.bad_char, c)
					break
				}
				if (a_dcn.length)
					syntax(1, "Decoration ignored");
︙
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
3045
3046
3047
3048
3049
3050
3051




3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064







-
-
-
-













	if (tp.length) {
		parse.tp = tp
		parse.tps = tps
		parse.tpn = tpn
	}
	if (sls.length)
		syntax(1, "Start of slur without note")
	if (a_dcn.length) {
		syntax(1, "Decoration without symbol")
		a_dcn = []
	}
	if (grace) {
		syntax(1, "No end of grace note sequence");
		curvoice.last_sym = grace.prev;
		curvoice.last_note = last_note_sav
		if (grace.prev)
			grace.prev.next = null
	}
	if (!no_eol && !cfmt.barsperstaff && !vover
	 && char_tb['\n'.charCodeAt(0)] == '\n')
		curvoice.eoln = true
	if (curvoice.eoln && cfmt.breakoneoln && curvoice.last_note)
		curvoice.last_note.beam_end = true
}
Changes to core/subs.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - subs.js - text output
//
// Copyright (C) 2014-2021 Jean-Francois Moine
// Copyright (C) 2014-2022 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
102
103
104
105
106
107
108



109

110
111
112
113
114
115
116
117
118
119
120

















121
122
123
124
125
126
127
128
129
102
103
104
105
106
107
108
109
110
111

112
113
114









115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131


132
133
134
135
136
137
138







+
+
+
-
+


-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-







}
// return the character width with the current font
function cwidf(c) {
	return cwid(c) * gene.curfont.swfac
}

// estimate the width and height of a string ..
var strwh

(function() {
var strwh = typeof document != "undefined" ?
    if (typeof document != "undefined") {

    // .. by the browser
    (function() {
	// (re)create a text element
    var	el = document.createElement('text')
	el.style.position = 'absolute'
	el.style.top = '-1000px'
	el.style.padding = '0'
	document.body.appendChild(el)

	return function(str) {

	// create a text element if not done yet
    var	el = abc2svg.eltxt

	// change the function
	strwh = function(str) {
		if (str.wh)
			return str.wh
		if (!el) {
			el = document.createElement('text')
			el.style.position = 'absolute'
			el.style.top = '-1000px'
			el.style.padding = '0'
			document.body.appendChild(el)
			abc2svg.eltxt = el	// reused after new Abc()
		}

		if (str.wh)
			return str.wh
	    var	c,
		font = gene.curfont,
		h = font.size,
		w = 0,
		n = str.length,
		i0 = 0,
		i = 0
︙
144
145
146
147
148
149
150
151

152
153
154
155
156
157
158
159
160
161
162












163
164
165
166
167





168
169
170
171
172
173
174
175
176
177

178
179
180

181
182
183
184
185
186
187
153
154
155
156
157
158
159

160











161
162
163
164
165
166
167
168
169
170
171
172
173




174
175
176
177
178
179
180
181
182



183
184

185
186
187

188
189
190
191
192
193
194
195







-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+
+




-
-
-


-
+


-
+







			case '&': return "&amp;"
			}
			return c		// &xxx;
		})

		while (1) {
			i = str.indexOf('$', i)
			if (i < 0)
			if (i >= 0) {
				break
			c = str[i + 1]
			if (c == '0') {
				font = gene.deffont
			} else if (c >= '1' && c <= '9') {
				font = get_font("u" + c)
			} else {
				i++
				continue
			}
			el.innerHTML = str.slice(i0, i);
				c = str[i + 1]
				if (c == '0') {
					font = gene.deffont
				} else if (c >= '1' && c <= '9') {
					font = get_font("u" + c)
				} else {
					i++
					continue
				}
			}

			el.innerHTML = str.slice(i0, i >= 0 ? i : undefined)
			w += el.clientWidth
//fixme: bad result if space(s) at end of string
			if (font.size > h)
				h = font.size;

//fixme: bad width if space(s) at end of string
			if (el.clientHeight > h)
				h = el.clientHeight
			if (i < 0)
				break
			el.style.font = style_font(font).slice(5);
			i += 2;
			i0 = i
		}
		el.innerHTML = str.slice(i0);
		w += el.clientWidth;

		return [w, h]
	}
    })() :
    } else {

    // .. by internal tables
    function(str) {
    strwh = function(str) {
    var	font = gene.curfont,
	swfac = font.swfac,
	h = font.size,
	w = 0,
	i, j, c,
	n = str.length

︙
212
213
214
215
216
217
218
219



220
221
222
223
224
225
226
227
228
229
230




231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247


248

249
250
251
252
253
254
255
220
221
222
223
224
225
226

227
228
229
230
231
232
233
234
235
236
237



238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

261
262
263
264
265
266
267
268







-
+
+
+








-
-
-
+
+
+
+

















+
+
-
+







				c = 'a'		// XML character reference
			}
			break
		}
		w += cwid(c) * swfac
	}
	return [w, h]
}
    }
  }
})()

// convert a string to a SVG text, handling the font changes
// The string size is memorized into the String.
function str2svg(str) {
	// check if the string is already converted
	if (typeof str == "object")
		return str

    var	o, n_font, wh,
	o_font = gene.curfont,
	c_font = o_font
    var	n_font, wh,
	o_font = gene.deffont,
	c_font = gene.curfont,
	o = ""

	// start a '<tspan>' element
	function tspan(nf, of) {
	    var	cl

		if (nf.class
		 && nf.name == of.name
		 && nf.size == of.size
		 && nf.weight == of.weight
		 && nf.style == of.style)
			cl = nf.class		// change only the class
		 else
			cl = font_class(nf)

		return '<tspan\n\tclass="' + cl + '">'
	} // tspan()

	if (c_font != o_font)
		o = tspan(c_font, o_font)
	o = str.replace(/<|>|&[^&\s]*?;|&|\$./g, function(c){
	o += str.replace(/<|>|&[^&\s]*?;|&|\$./g, function(c){
			switch (c) {
			case '<': return "&lt;"
			case '>': return "&gt;"
			case '&': return "&amp;"
			default:
				if (c[0] != '$')
					break
︙
309
310
311
312
313
314
315
316

317
318
319


320
321
322

323
324
325
326
327
328
329
322
323
324
325
326
327
328

329
330


331
332
333
334

335
336
337
338
339
340
341
342







-
+

-
-
+
+


-
+







		str,		// string or object String with attribute 'wh'
		action,		// default: align left
		w,		// needed for justify
		wh) {		// optional [width, height]
	if (!wh)
		wh = str.wh || strwh(str)

	output += '<text class="' + font_class(gene.curfont)
	output += '<text class="' + font_class(gene.deffont)
	if (action != 'j' && str.length > 5
	 && gene.curfont.wadj)
		output += '" lengthAdjust="' + gene.curfont.wadj +
	 && gene.deffont.wadj)
		output += '" lengthAdjust="' + gene.deffont.wadj +
			'" textLength="' + wh[0].toFixed(1);
	output += '" x="';
	out_sxsy(x, '" y="', y + wh[1] * .2)	// a bit upper for the descent
	out_sxsy(x, '" y="', y)
	switch (action) {
	case 'c':
		x -= wh[0] / 2;
		output += '" text-anchor="middle">'
		break
	case 'j':
		output += '" textLength="' + w.toFixed(1) + '">'
︙
450
451
452
453
454
455
456

457

458
459

460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480

481
482
483

484

485
486
487
488
489
490
491
492
493
494
495
496

497
498
499
500
501
502
503
463
464
465
466
467
468
469
470

471
472

473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493

494
495
496
497
498

499
500
501
502
503
504

505
506
507
508
509

510
511
512
513
514
515
516
517







+
-
+

-
+




















-
+



+
-
+





-





-
+







				if (i == text.length)
					break
			} else {
				if (i < 0)
					str = text.slice(j)
				else
					str = text.slice(j, i)
				ww = strwh(str)
				vskip(strwh(str)[1]  * cfmt.lineskipfac
				vskip(ww[1] * cfmt.lineskipfac
					+ font.pad * 2)
				xy_str(x, font.pad, str, action)
				xy_str(x, font.pad + ww[1] * .2, str, action)
				if (i < 0)
					break
			}
			j = i + 1
		}
		vskip(parskip);
		blk_flush()
		break
	case 'f':
	case 'j':
		j = 0
		while (1) {
			i = text.indexOf('\n\n', j)
			if (i < 0)
				words = text.slice(j)
			else
				words = text.slice(j, i);
			words = words.split(/\s+/);
			w = k = wh = 0
			for (j = 0; j < words.length; j++) {
				ww = strwh(words[j])
				ww = strwh(words[j] + ' ')	// &nbsp;
				w += ww[0]
				if (w >= strlw) {
					vskip(wh * cfmt.lineskipfac)
					xy_str(0, ww[1] * .2,
					xy_str(0, 0, words.slice(k, j).join(' '),
						words.slice(k, j).join(' '),
						action, strlw)
					k = j;
					w = ww[0]
					wh = 0
				}
				w += cwidf(' ')
				if (ww[1] > wh)
					wh = ww[1]
			}
			if (w != 0) {			// last line
				vskip(wh * cfmt.lineskipfac)
				xy_str(0, 0, words.slice(k).join(' '))
				xy_str(0, ww[1] * .2, words.slice(k).join(' '))
			}
			vskip(parskip);
			blk_flush()
			if (i < 0)
				break
			while (text[i + 2] == '\n') {
				vskip(lineskip);
︙
539
540
541
542
543
544
545

546
547

548
549

550
551
552
553
554
555
556
553
554
555
556
557
558
559
560
561

562
563

564
565
566
567
568
569
570
571







+

-
+

-
+







					break
			}
			k = i
			while (p[i] == ' ')
				i++
		}

	    var	y = gene.curfont.size * .22		// descent
		if (k != 0)
			xy_str(x, 0, p.slice(0, k), 'r')
			xy_str(x, y, p.slice(0, k), 'r')
		if (i < p.length)
			xy_str(x + 5, 0, p.slice(i), 'l')
			xy_str(x + 5, y, p.slice(i), 'l')
	} // put_wline()

	set_font("words")
	vskip(cfmt.wordsspace)
	svg_flush()

	// estimate the width of the lines
︙
681
682
683
684
685
686
687
688

689
690
691
692
693
694
695
696
697
698
699
700
701
702

703
704
705
706
707
708
709
710







-
+







	P: "parts",
	Q: "tempo",
	R: "info",
	T: "title",
	X: "title"
}
function write_headform(lwidth) {
	var	c, font, font_name, align, x, y, sz,
    var	c, font, font_name, align, x, y, sz, w,
		info_val = {},
		info_font = Object.create(info_font_init),
		info_sz = {
			A: cfmt.infospace,
			C: cfmt.composerspace,
			O: cfmt.composerspace,
			R: cfmt.infospace
︙
Changes to core/svg.js.
︙
21
22
23
24
25
26
27
28

29
30
31
32
33
34
35
21
22
23
24
25
26
27

28
29
30
31
32
33
34
35







-
+







	style = '\
\n.stroke{stroke:currentColor;fill:none}\
\n.bW{stroke:currentColor;fill:none;stroke-width:1}\
\n.bthW{stroke:currentColor;fill:none;stroke-width:3}\
\n.slW{stroke:currentColor;fill:none;stroke-width:.7}\
\n.slthW{stroke:currentColor;fill:none;stroke-width:1.5}\
\n.sW{stroke:currentColor;fill:none;stroke-width:.7}\
\n.box{outline: 1px solid black;outline-offset: 1px}',
\n.box{outline:1px solid black;outline-offset:1px}',
	font_style = '',
	posx = cfmt.leftmargin / cfmt.scale,	// default x offset of the images
	posy = 0,		// y offset in the block
	img = {			// image
		width: cfmt.pagewidth,	// width
		lm: cfmt.leftmargin,	// left and right margins
		rm: cfmt.rightmargin
︙
304
305
306
307
308
309
310
311

312
313
314
315
316
317
318
319
304
305
306
307
308
309
310

311

312
313
314
315
316
317
318







-
+
-







			output += 'transform="translate(0,' +
					(posy - stv_g.dy).toFixed(1) +
				') scale(' + stv_g.scale.toFixed(2) + ')"'
	}
	if (stv_g.color) {
		if (stv_g.scale != 1)
			output += ' ';
		output += 'color="' + stv_g.color +
		output += 'color="' + stv_g.color + '"'
			'" fill="' + stv_g.color + '"'
	}
	output += ">\n";
	stv_g.started = true
}

/* set the color */
function set_color(color) {
︙
367
368
369
370
371
372
373





374
375
376
377
378
379
380
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384







+
+
+
+
+







	stv_g.v = s.v;
	set_g()
}

// -- set the staff output buffer and scale when delayed output
function set_dscale(st, no_scale) {
	if (output) {
		if (stv_g.started) {	// close the previous sequence
			stv_g.started = false
			glout()
			output += "</g>\n"
		}
		if (stv_g.st < 0) {
			staff_tb[0].output += output
		} else if (stv_g.scale == 1) {
			staff_tb[stv_g.st].output += output
		} else {
			staff_tb[stv_g.st].sc_out += output
		}
︙
728
729
730
731
732
733
734
735

736
737
738
739
740
741
742
732
733
734
735
736
737
738

739
740
741
742
743
744
745
746







-
+







		n = ((w - 15) / d) | 0
	else
		n = 0;
	x += (w - d * n - 5) / 2;
	out_XYAB('<path class="stroke" stroke-width="1.2"\n\
	stroke-dasharray="5,A"\n\
	d="mX YhB"/>\n',
		x, y + 6,		// set the line a bit upper
		x, y + 4,		// set the line a bit upper
		Math.round((d - 5) / stv_g.scale), d * n + 5)
}
// stem [and flags]
function out_stem(x, y, h, grace,
		  nflags, straight) {	// optional
//fixme: dx KO with half note or longa
	var	dx = grace ? GSTEM_XOFF : 3.5,
︙
847
848
849
850
851
852
853
854

855
856
857
858
859
860
861
851
852
853
854
855
856
857

858
859
860
861
862
863
864
865







-
+







// tuplet bracket with number - the staves are not defined
function out_tubrn(x, y, dx, dy, up, str) {
    var	dxx,
	sw = str.length * 10,
	h = up ? -3 : 3;

	set_font("tuplet")
	xy_str(x + dx / 2, y + dy / 2 - gene.curfont.size * .5 + 2,
	xy_str(x + dx / 2, y + dy / 2 - gene.curfont.size * .1,
		str, 'c')
		dx /= stv_g.scale
	if (!up)
		y += 6;
	output += '<path class="stroke" d="m';
	out_sxsy(x, ' ', y);
	dxx = dx - sw + 1
︙
872
873
874
875
876
877
878
879

880
881
882
883
884
885
886
876
877
878
879
880
881
882

883
884
885
886
887
888
889
890







-
+







	out_sxsy(x, ' ', y - h);
	output += 'l' + dx.toFixed(1) + ' ' + (-dy).toFixed(1) + '"/>\n'

}
// underscore line
function out_wln(x, y, w) {
	out_XYAB('<path class="stroke" stroke-width="0.8" d="mX YhF"/>\n',
		x, y + 3, w)
		x, y + 1, w)
}

// decorations with string
var deco_str_style = {
crdc:	{
		dx: 0,
		dy: 5,
︙
1069
1070
1071
1072
1073
1074
1075

1076

1077
1078






1079
1080
1081





1082
1083
1084
1085

1086
1087
1088
1089
1090
1091
1092
1093
1094
1095

1096

1097
1098






1099
1100
1101





1102
1103
1104

1105
1106
1107
1108
1109
1110
1111
1112
1113
1073
1074
1075
1076
1077
1078
1079
1080

1081
1082
1083
1084
1085
1086
1087
1088
1089



1090
1091
1092
1093
1094
1095
1096
1097

1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109

1110
1111
1112
1113
1114
1115
1116
1117
1118



1119
1120
1121
1122
1123
1124
1125

1126


1127
1128
1129
1130
1131
1132
1133







+
-
+


+
+
+
+
+
+
-
-
-
+
+
+
+
+



-
+










+
-
+


+
+
+
+
+
+
-
-
-
+
+
+
+
+


-
+
-
-







	if (deco_val_tb[name])
		deco_val_tb[name](x, y, val, defl)
	else
		error(1, null, "No function for decoration '$1'", name)
}

function out_glisq(x2, y2, de) {
    var	ar, a, len,
	var	de1 = de.start,
	de1 = de.start,
		x1 = de1.x,
		y1 = de1.y + staff_tb[de1.st].y,
		dx = x2 - x1,
		dy = self.sh(y1 - y2)

	if (!stv_g.g)
		dx /= stv_g.scale

		ar = Math.atan2(y1 - y2, x2 - x1),
		a = ar / Math.PI * 180,
		len = (x2 - x1) / Math.cos(ar);
	ar = Math.atan2(dy, dx)
	a = ar / Math.PI * 180
	len = (dx - (de1.s.dots ? 13 + de1.s.xmx : 8)
		- 8 - (de.s.notes[0].shac || 0))
			/ Math.cos(ar)

	g_open(x1, y1, a);
	x1 = de1.s.dots ? 13 + de1.s.xmx : 8;
	len = (len - x1 - 6) / 6 | 0
	len = len / 6 | 0
	if (len < 1)
		len = 1
	while (--len >= 0) {
		xygl(x1, 0, "ltr");
		x1 += 6
	}
	g_close()
}

function out_gliss(x2, y2, de) {
    var	ar, a, len,
	var	de1 = de.start,
	de1 = de.start,
		x1 = de1.x,
		y1 = de1.y + staff_tb[de1.st].y,
		dx = x2 - x1,
		dy = self.sh(y1 - y2)

	if (!stv_g.g)
		dx /= stv_g.scale

		ar = -Math.atan2(y2 - y1, x2 - x1),
		a = ar / Math.PI * 180,
		len = (x2 - x1) / Math.cos(ar);
	ar = Math.atan2(dy, dx)
	a = ar / Math.PI * 180
	len = (dx - (de1.s.dots ? 13 + de1.s.xmx : 8)
		- 8 - (de.s.notes[0].shac || 0))
			/ Math.cos(ar)

	g_open(x1, y1, a);
	x1 = de1.s.dots ? 13 + de1.s.xmx : 8;
	xypath(de1.s.dots ? 13 + de1.s.xmx : 8, 0)
	len -= x1 + 8;
	xypath(x1, 0);
	output += 'h' + len.toFixed(1) + '" stroke-width="1"/>\n';
	g_close()
}

var deco_l_tb = {
	glisq: out_glisq,
	gliss: out_gliss
︙
1233
1234
1235
1236
1237
1238
1239
1240

1241
1242
1243
1244
1245
1246
1247

1248
1249
1250
1251
1252
1253
1254
1253
1254
1255
1256
1257
1258
1259

1260
1261
1262
1263
1264
1265
1266

1267
1268
1269
1270
1271
1272
1273
1274







-
+






-
+







		bh = gene.curfont.size + 4
	}

//fixme: xy_str() cannot be used because <tspan> in s.tempo_str
//fixme: then there cannot be font changes by "$n" in the Q: texts
	output += '<text class="' + font_class(gene.curfont) +
		'" x="'
	out_sxsy(x, '" y="', y + gene.curfont.size * .2)
	out_sxsy(x, '" y="', y + gene.curfont.size * .22)
	output += '">' + s.tempo_str + '</text>\n'

	if (bh) {
		gene.curfont.box = true
		output += '<rect class="stroke" x="'
		out_sxsy(x - 2, '" y="', y + bh - 1)
		output += '" width="' + (s.tempo_wh[0] + 2).toFixed(1) +
		output += '" width="' + (s.tempo_wh[0] + 4).toFixed(1) +
			'" height="' + bh.toFixed(1) +
			'"/>\n'
	}

	// don't display anymore
	s.invis = true
} // writempo()
︙
1262
1263
1264
1265
1266
1267
1268
1269

1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302













1303
1304
1305
1306
1307
1308
1309
1282
1283
1284
1285
1286
1287
1288

1289
1290
1291
1292
1293









1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311


1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331







-
+




-
-
-
-
-
-
-
-
-


















-
-
+
+
+
+
+
+
+
+
+
+
+
+
+







function svg_flush() {
	if (multicol || !output || !user.img_out || posy == 0)
		return

    var	i, font,
	head = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"\n\
	xmlns:xlink="http://www.w3.org/1999/xlink"\n\
	color="',
	fill="currentColor" stroke-width=".7"',
	g = ''

	glout()

	if (cfmt.fgcolor)
		head += cfmt.fgcolor + '" fill="' + cfmt.fgcolor + '"'
	else
		head += 'black"';
	head += ' stroke-width=".7"'

	if (cfmt.bgcolor)
		head += ' style="background-color: ' + cfmt.bgcolor + '"';

	font = get_font("music")
	head += ' class="' + font_class(font) +
		' tune' + tunes.length + '"\n'	// tune index for play

	posy *= cfmt.scale
	if (user.imagesize) {
		head += user.imagesize +
			' viewBox="0 0 ' + img.width.toFixed(0) + ' ' +
			 posy.toFixed(0) + '">\n'
	} else {
		head += ' viewBox="0 0 ' + img.width.toFixed(0) + ' ' +
			posy.toFixed(0) +
			'" width="' + img.width.toFixed(0) +
			'px" height="' + posy.toFixed(0) + 'px">\n'
	}

	head += fulldefs

	if (style || font_style)
		head += '<style>' + font_style + style + '\n</style>\n'
	if (style || font_style) {
		head += '<style>' + font_style
		if (cfmt.fgcolor || cfmt.bgcolor) {
			head += '\n.f' + font.fid + (cfmt.fullsvg || '')
				+ '{'
				+ (cfmt.fgcolor ? ('color:' + cfmt.fgcolor + ';')
						: '')
				+ (cfmt.bgcolor ? ('background-color:' + cfmt.bgcolor)
						: '')
				+ '}'
		}
		head += style + '\n</style>\n'
	}

	if (defs)
		head += '<defs>' + defs + '\n</defs>\n'

	// if %%pagescale != 1, do a global scale
	// (with a container: transform scale in <svg> does not work
	//	the same in all browsers)
︙
1324
1325
1326
1327
1328
1329
1330
1331

1332
1333
1334
1335
1336
1337
1338
1346
1347
1348
1349
1350
1351
1352

1353
1354
1355
1356
1357
1358
1359
1360







-
+







			'<div class="nobrk newpage">')
		blkdiv = -1		// block started
	}
	user.img_out(head + output + g + "</svg>");
	output = ""

	font_style = ''
	if (cfmt.fullsvg && typeof document == "undefined") {
	if (cfmt.fullsvg) {
		defined_glyph = {}
		for (i = 0; i < font_tb.length; i++)
			font_tb[i].used = false
	} else {
		style = '';
		fulldefs = ''
	}
︙
Changes to core/tail.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - tail.js
//
// Copyright (C) 2014-2020 Jean-Francois Moine
// Copyright (C) 2014-2022 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
52
53
54
55
56
57
58
59

60
61
62
63
64
65
66
52
53
54
55
56
57
58

59
60
61
62
63
64
65
66







-
+







Abc.prototype.get_multi = function() { return multicol };
Abc.prototype.get_newpage = function() {
	if (block.newpage) {
		block.newpage = false;
		return true
	}
};
Abc.prototype.get_posy = function() { var t = posy; posy = 0; return t };
Abc.prototype.get_posy = function() { return posy }
Abc.prototype.get_staff_tb = function() { return staff_tb };
Abc.prototype.get_top_v = function() { return par_sy.top_voice };
Abc.prototype.get_tsfirst = function() { return tsfirst };
Abc.prototype.get_unit = get_unit;
Abc.prototype.get_voice_tb = function() { return voice_tb };
Abc.prototype.glout = glout
Abc.prototype.info = function() { return info };
︙
75
76
77
78
79
80
81

82

83
84
85
86
87
88
89
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91







+

+







Abc.prototype.psxygl = empty_function;
Abc.prototype.set_cur_sy = function(sy) { cur_sy = sy };
Abc.prototype.set_dscale = set_dscale;
Abc.prototype.set_font = set_font;
Abc.prototype.set_a_gch = function(s, a) { a_gch = a; csan_add(s) }
Abc.prototype.set_hl = set_hl
Abc.prototype.set_pagef = function() { blkdiv = 1 }
Abc.prototype.set_realwidth = function(v) { realwidth = v }
Abc.prototype.set_scale = set_scale;
Abc.prototype.set_sscale = set_sscale
Abc.prototype.set_tsfirst = function(s) { tsfirst = s };
Abc.prototype.set_v_param = set_v_param;
Abc.prototype.strwh = strwh;
Abc.prototype.stv_g = function() { return stv_g };
Abc.prototype.svg_flush = svg_flush;
Abc.prototype.syntax = syntax;
Abc.prototype.tunes = tunes
︙
Changes to core/tune.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - tune.js - tune generation
//
// Copyright (C) 2014-2022 Jean-Francois Moine
// Copyright (C) 2014-2023 Jean-Francois Moine
//
// This file is part of abc2svg-core.
//
// abc2svg-core is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
52
53
54
55
56
57
58


59
60
61
62
63
64

65
66
67
68
69

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
52
53
54
55
56
57
58
59
60
61
62
63
64
65

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90






91
92
93
94
95
96
97







+
+





-
+





+


















-
-
-
-
-
-







	    for (opt in parse.tune_v_opts) {
		if (parse.tune_v_opts.hasOwnProperty(opt))
			vfilt(parse.tune_v_opts[opt], opt)
	}
}

/* -- link a ABC symbol into the current voice -- */
// if a voice is ignored (not in %%staves) don't link the symbol
//	but update the time for P: and Q:
function sym_link(s) {
    var	tim = curvoice.time

	if (!s.fname)
		set_ref(s)
	parse.last_sym = s
    if (!curvoice.ignore) {
	s.prev = curvoice.last_sym
	if (curvoice.last_sym)
		curvoice.last_sym.next = s
	else
		curvoice.sym = s
    }
	curvoice.last_sym = s
	s.v = curvoice.v;
	s.p_v = curvoice;
	s.st = curvoice.cst;
	s.time = tim
	if (s.dur && !s.grace)
		curvoice.time += s.dur;
	parse.ufmt = true
	s.fmt = cfmt				// global parameters
	s.pos = curvoice.pos
	if (curvoice.second)
		s.second = true
	if (curvoice.floating)
		s.floating = true
	if (curvoice.eoln) {
		s.soln = true
		curvoice.eoln = false
	}
	if (parse.part && parse.part[tim]) {
		if (!parse.part[tim].done) {
			s.part = parse.part[tim]
			parse.part[tim].done = true
		}
	}
}

/* -- add a new symbol in a voice -- */
function sym_add(p_voice, type) {
	var	s = {
			type:type,
			dur:0
︙
118
119
120
121
122
123
124
125
126


127
128
129
130
131
132
133

134
135
136
137
138
139
140
115
116
117
118
119
120
121


122
123
124
125
126
127
128
129

130
131
132
133
134
135
136
137







-
-
+
+






-
+








/* -- sort all symbols by time and vertical sequence -- */
// weight of the symbols !! depends on the symbol type !!
var w_tb = new Uint8Array([
	5,	// bar
	1,	// clef
	8,	// custos
	0,	// (free)
	4,	// grace
	6,	// sm (sequence marker, after bar)
	0,	// grace (must be null)
	2,	// key
	3,	// meter
	9,	// mrest
	9,	// note
	0,	// part
	9,	// rest
	4,	// space
	4,	// space (before bar)
	0,	// staves
	7,	// stbrk
	0,	// tempo
	0,	// (free)
	0,	// block
	0	// remark
])
︙
158
159
160
161
162
163
164















































165
166
167
168
169
170
171
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







		sy: sy,
		next: p_voice.sym,
		seqst: true
	}

	if (!p_voice.sym)
		return

	// check if different bars at the same time
	function b_chk() {
	    var	ir, bt, s, s2, v

		ir = 0
		bt = ""
		while (1) {
			v = vn[ir++]
			if (v == undefined)
				break
			s = vtb[v]
			if (!s || !s.bar_type || s.invis)
				continue
			if (!bt) {
				bt = s.bar_type
				continue
			}
			if (s.bar_type != bt)
				break
		}
		if (!v)
			return			// no problem

		
		if (bt == "::" || bt == ":|") {
			ir = 0
			while (1) {
				v = vn[ir++]
				if (v == undefined)
					break
				s = vtb[v]
				if (!s || s.invis
				 || s.bar_type != "::")
					continue
				s2 = clone(s)
				s.bar_type = ":|"
				s2.bar_type = "|:"
				s2.next = s.next
				s2.prev = s
				s.next = s2
			}
		} else {
			error(2, s, "Different bars $1 and $2",
				bt, s.bar_type)
		}
	} // b_chk()

	prev.fmt = fmt = p_voice.sym.fmt	// starting format

	// set the first symbol of each voice
	for (v = 0; v < nv; v++) {
		s = voice_tb[v].sym
		vtb[v] = s
︙
195
196
197
198
199
200
201


















202
203
204
205
206
207
208

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226


227
228
229
230
231
232
233
234
235
236




237
238
239
240
241
242
243
244
245
246







247
248
249
250
251
252
253
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269

270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312


313
314
315
316
317
318
319
320
321
322
323
324
325
326







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
+


















+
+










+
+
+
+








-
-
+
+
+
+
+
+
+







		else
			p_voice.last_sym = s
		s.prev.next = s
		s.fmt = fmt
		glovar.tempo = null
		vtb[v] = s
	}

	// if only one voice, quickly create the time links
	if (nv == 1) {
		s = tsfirst
		s.ts_next = s.next
		while (1) {
			s = s.next
			if (!s)
				return
			s.ts_prev = s.prev
			s.ts_next = s.next
			if (s.time != s.prev.time
			 || w_tb[s.prev.type]
			 || s.type == C.GRACE && s.prev.type == C.GRACE)
				s.seqst = 1 //true
		}
		// not reached
	}

	// loop on the symbols of all voices
	while (1) {
		if (new_sy) {
			sy = new_sy;
			new_sy = null;
			vn = []
			vn.length = 0
			for (v = 0; v < nv; v++) {
				if (!sy.voices[v])
					continue
				vn[sy.voices[v].range] = v
			}
		}

		/* search the min time and symbol weight */
		wmin = time = 10000000		// big int
		ir = 0
		while (1) {
			v = vn[ir++]
			if (v == undefined)
				break
			s = vtb[v]
			if (!s || s.time > time)
				continue
			w = w_tb[s.type]
			if (s.type == C.GRACE && s.next && s.next.type == C.GRACE)
				w--
			if (s.time < time) {
				time = s.time;
				wmin = w
			} else if (w < wmin) {
				wmin = w
			}
		}

		if (wmin > 127)
			break			// done

		// check the type of the measure bars
		if (wmin == 5)			// weight of bars
			b_chk()

		/* link the vertical sequence */
		ir = 0
		while (1) {
			v = vn[ir++]
			if (v == undefined)
				break
			s = vtb[v]
			if (!s || s.time != time
			 || w_tb[s.type] != wmin)
			if (!s)
				continue
			w = w_tb[s.type]
			if (s.type == C.GRACE && s.next && s.next.type == C.GRACE)
				w--
			if (s.time != time
			 || w != wmin)
				continue
			if (s.type == C.STAVES)
				new_sy = s.sy
			if (fl) {
				fl = 0;
				s.seqst = true
			}
︙
318
319
320
321
322
323
324
325
326


327
328
329
330
331
332

333

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354































355
356
357
358
359
360
361
391
392
393
394
395
396
397


398
399




400
401
402

403






404
405
406
407
408
409
410








411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448







-
-
+
+
-
-
-
-


+
-
+
-
-
-
-
-
-







-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







		p_voice = voice_tb[v]
		if (!sys_chg) {			// if not %%score
			delete p_voice.eoln
			while (1) {		// set the end of slurs
				sl = p_voice.sls.shift()
				if (!sl)
					break
				s = sl.note.s
				for (s2 = s.next; s2; s2 = s2.next) {
				s = sl.ss
//					error(1, s, "Lack of ending slur(s)")
					if (s2.bar_type && s2.bar_type[0] == ':')
						break
				}
				if (s2) {
					if (!s.sls)
						s.sls = []
				sl.loc = 'o'		// no slur end
					s.sls.push({
				s.sls.push(sl)
						ty: sl.ty,
						note: {s: s2}
					})
				} else {
					error(1, s, "Lack of ending slur(s)")
				}
			}
		} // not %%score
		for (s = p_voice.sym; s; s = s.next) {
			if (s.time >= staves_found)
				break
		}
		for ( ; s; s = s.next) {
			switch (s.type) {
			case C.GRACE:
				// with w_tb[C.BAR] = 4 and w_tb[C.GRACE] = 3,
				// the grace notes go after the bar;
				// if before a bar, change the grace time
				if (s.next && s.next.type == C.BAR)
					s.time--


			// if the symbol has a sequence weight smaller than the bar one
			// and if there a time skip,
			// add an invisible bar before it
			if (w_tb[s.type] < 5
			 && s.type != C.STAVES
			 && s.type != C.CLEF
			 && s.time			// not at start of tune
			 && (!s.prev || s.time > s.prev.time + s.prev.dur)) {
				s2 = {
					type: C.BAR,
					bar_type: "[]",
					v: s.v,
					p_v: s.p_v,
					st: s.st,
					time: s.time,
					dur:0,
					next: s,
					prev: s.prev,
					fmt: s.fmt,
					invis: 1
				}
				if (s.prev)
					s.prev.next = s2
				else
					voice_tb[s.v].sym = s2
				s.prev = s2
			}

			switch (s.type) {
			case C.GRACE:
				if (!cfmt.graceword)
					continue
				for (s2 = s.next; s2; s2 = s2.next) {
					switch (s2.type) {
					case C.SPACE:
						continue
					case C.NOTE:
︙
409
410
411
412
413
414
415
416


417
418
419
420

















421
422
423
424
425
426
427
496
497
498
499
500
501
502

503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532







-
+
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







	par_sy.next = sy_new;
	par_sy = sy_new
}

/* -- set the bar numbers -- */
// (possible hook)
Abc.prototype.set_bar_num = function() {
    var	s, s2, tim, rep_tim, k, n,
    var	s, s2, rep_tim, k, n, nu, txt,
	tim = 0,			// time of the previous bar
	bar_num = gene.nbar,
	bar_tim = 0,			// time of previous repeat variant
	ptim = 0,			// time of previous bar
	wmeasure = voice_tb[cur_sy.top_voice].meter.wmeasure

	// check the measure duration
	function check_meas() {
	    var	s3

		if (tim > ptim + wmeasure
		 && s.prev.type != C.MREST)
			return 1 //true

		// the measure is too short,
		// check if there is a bar a bit further
		for (s3 = s.next; s3 && s3.time == s.time; s3 = s3.next)
			;
		for ( ; s3 && !s3.bar_type; s3 = s3.next)
			;
		return s3 && (s3.time - bar_tim) % wmeasure
	}

	// don't count a bar at start of tune
	for (s = tsfirst; ; s = s.ts_next) {
		if (!s)
			return
		switch (s.type) {
		case C.METER:
︙
448
449
450
451
452
453
454
455
456
457
458


459
460
461
462
463
464
465
466


467













468
469
470
471
472
473
474
475
476

477
478

479
480
481
482
483

484

485
486
487
488
489
490
491
492


493
494
495
496
497
498
499

500
501


502
503
504
505
506

507
508
509
510
511
512
513
514
515
516
517
518
519

520
521
522
523
524
525
526
553
554
555
556
557
558
559


560
561
562
563
564
565
566
567
568
569


570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593

594
595

596
597
598
599
600
601
602

603
604
605
606
607
608
609


610
611
612
613
614
615
616
617
618
619


620
621
622
623
624
625

626
627
628
629
630
631
632
633
634
635
636
637


638
639
640
641
642
643
644
645







-
-


+
+






-
-
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+








-
+

-
+





+
-
+






-
-
+
+







+
-
-
+
+




-
+











-
-
+







			}
			break
		}
	}

	// set the measure number on the top bars
	for ( ; s; s = s.ts_next) {
		if (!s.seqst)
			continue
		switch (s.type) {
		case C.METER:
			if (s.time == bar_tim)
				break		// already seen
		    if (wmeasure != 1)		// if not M:none
			bar_num += (s.time - bar_tim) / wmeasure
			bar_tim = s.time
			wmeasure = s.wmeasure
			break
		case C.BAR:
			if (s.invis)
				break
			if (s.time <= tim)
				break			// already seen
			tim = s.time

			nu = 1 //true			// no num update
			txt = ""
			for (s2 = s; s2; s2 = s2.next) {
				if (s2.time > tim || s2.dur)
					break
				if (!s2.bar_type)
					continue
				if (s2.bar_type != '[')
					nu = 0 //false	// do update
				if (s2.text)
					txt = s2.text
			}
			if (s.bar_num) {
				bar_num = s.bar_num	// (%%setbarnb)
				ptim = bar_tim = tim
				continue
			}
			if (wmeasure == 1) {		// if M:none
				if (s.bar_dotted)
					continue
				if (s.text) {
				if (txt) {
					if (!cfmt.contbarnb) {
						if (s.text[0] == '1')
						if (txt[0] == '1')
							rep_tim = bar_num
						else
							bar_num = rep_tim
					}
				}
				if (!nu)
				s.bar_num = ++bar_num
					s.bar_num = ++bar_num
				continue
			}

			n = bar_num + (tim - bar_tim) / wmeasure
			k = n - (n | 0)
			if (cfmt.checkbars
			 && ((k && !s.bar_dotted && s.next)
			  || (tim > ptim + wmeasure && s.prev.type != C.MREST)))
			 && k
			 && check_meas())
				error(0, s, "Bad measure duration")
			if (tim > ptim + wmeasure) {	// if more than one measure
				n |= 0
				k = 0
				bar_tim = tim		// re-synchronize
				bar_num = n
			}

			if (s.text) {
				if (s.text[0] == '1') {
			if (txt) {
				if (txt[0] == '1') {
					if (cfmt.contbarnb)
						rep_tim = bar_tim + k * wmeasure
					else
						rep_tim = tim
					if (!k)
					if (!nu)
						s.bar_num = n
				} else {
					if (cfmt.contbarnb)
						bar_tim = rep_tim
					else
						bar_tim += tim - rep_tim
					n = bar_num + (tim - bar_tim) / wmeasure
					if (n == (n | 0))
						s.bar_num = n
				}
			} else {
				if (k)
					n -= k
				n |= 0
				s.bar_num = n
			}
			if (!k)
				ptim = tim
			break
		}
	}
︙
648
649
650
651
652
653
654
655

656
657

658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687

688
689
690
691

692
693
694
695


696
697
698
699


700
701
702
703
704
705
706
707
708
709
710
711
712

713
714
715
716
717
718
719
720
721
722
723

724
725

726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755



756
757

758
759
760

761
762
763

764
765
766
767


768
769
770
771

772
773
774

775
776
777

778
779
780
781

782
783

784
785
786
787
788
789
790
791
792

793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809

810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830




831
832

833
834
835
836
837
838
839



840
841
842
843
844
845

846
847
848
849
850
851
852
853


854
855
856
857

858
859
860

861
862

863
864

865
866

867
868
869
870
871
872
873
767
768
769
770
771
772
773

774
775

776
777
778
779
780
781
782
783


















784
785
786
787

788




789




790
791




792
793


794
795
796
797
798






799











800


801






























802
803
804


805



806



807




808
809




810



811



812




813


814









815

















816





















817
818
819
820


821







822
823
824






825








826
827




828



829


830


831


832
833
834
835
836
837
838
839







-
+

-
+







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-




-
+
-
-
-
-
+
-
-
-
-
+
+
-
-
-
-
+
+
-
-





-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
-
-
-
+
-
-
-
+
-
-
-
-
+
+
-
-
-
-
+
-
-
-
+
-
-
-
+
-
-
-
-
+
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
+
-
-
-
-
+
-
-
-
+
-
-
+
-
-
+
-
-
+







			}
			map[2] = a[i]
			break
		}
	}
}

// set the transposition in the previous or first key signature
// set the transposition in the first key signature or in a new key
function set_transp() {
    var	s, transp, sndtran
    var	s

	if (curvoice.ckey.k_bagpipe || curvoice.ckey.k_drum)
		return

	if (cfmt.transp && curvoice.shift)	// if %%transpose and shift=
		syntax(0, "Mix of old and new transposition syntaxes");


	if (cfmt.transp != undefined
	 || curvoice.transp != undefined
	 || curvoice.shift != undefined)
		transp = (cfmt.transp || 0) +	 // %%transpose
			(curvoice.transp || 0) + // score= / sound= / instrument=
			(curvoice.shift || 0)	 // shift=
	if (curvoice.sndtran != undefined
	 || curvoice.sndsh != undefined)
		sndtran = (curvoice.sndtran || 0) +
			(curvoice.sndsh || 0)
	if (transp == undefined) {
		if (sndtran == undefined)
			return
	} else {
		curvoice.vtransp = transp
	}

	if (is_voice_sig()) {			// if no symbol yet
		curvoice.okey.fmt = cfmt
		curvoice.key = s = clone(curvoice.okey)
	} else {
		s = curvoice.last_sym
		s = clone(curvoice.ckey)
		while (1) {	// set the transposition in the previous K:
			if (s.type == C.KEY)
				break
			s = s.prev
		s.k_old_sf = s.k_sf
			if (!s) {
				s = curvoice.key	// first key
				break
			}
		sym_link(s)
	}
		}
	}
	if (transp != undefined)
		s.k_transp = transp
	delete s.invis			// needed if K:C/none at start of tune
	key_transp(s)
	if (sndtran != undefined)
		s.k_sndtran = sndtran
	curvoice.ckey = clone(s)
	if (curvoice.key.k_none)
		s.k_sf = 0
}

/* transpose a note / chord */
function note_transp(s, sk, note) {
    var	ak, an, d, b40,
	n = note.pit,
	a = note.acc

// set the control values (P: and Q:)
	if (typeof a == "object") {
		d = a[1]
		a = a[0]
		if (d != 2) {
			error(1, s, "Microtone transposition not coded")
			return
		}
	}
	if (!a && sk.k_a_acc)			// if accidental list
		a = sk.k_map[(n + 19) % 7]	// invisible accidental

function set_ctrl() {
	b40 = abc2svg.pab40(n, a) + sk.k_transp	// base-40 transposition

    var	s, tim, e
	note.pit = abc2svg.b40p(b40)		// new pitch

	if (!a) {				// if no old accidental
		if (!sk.k_a_acc			// if no accidental list
		 && !sk.k_none)			// and normal key
			return			// same accidental (in the key)
	}

	an = abc2svg.b40a(b40)			// new accidental
	if (a) {
		if (sk.k_a_acc) {		// if accidental list
			ak = sk.k_map[(note.pit + 19) % 7]
			if (ak == an)
				an = 0		// accidental in the key
		}
		if (!an)
			an = 3
	} else if (sk.k_none) {			// if no key
		if (acc_same_pitch(s, note.pit)) // and accidental from previous notes
			return			// no change
	} else if (sk.k_a_acc) {		// if accidental list
		if (acc_same_pitch(s, note.pit)) // and accidental from previous notes
			return			// no change
		ak = sk.k_map[(note.pit + 19) % 7]
		if (ak)
			an = 3		// natural
	} else {
		return			// same accidental (in the key)
	}
	if (d && an != a) {

	for (tim in parse.ctrl) {
//		if (!parse.ctrl.hasOwnProperty(tim))
		switch (Number(a)) {
		case -3:
//			continue
			switch (an) {
			case -2:
				an = -1
		e = parse.ctrl[tim]
				break
			case 3:			// natural
				an = -3
		s = tsfirst
				note.pit++
				break
			case 2:
				an = -1
		while (s.next && s.next.time < tim)
			s = s.next
				break
			}
			break
		case -1:
		while (s && s.time < tim)
			switch (an) {
			case -2:
				an = -3
			s = s.ts_next
				break
			case 3:			// natural
				an = 1
		if (!s) {
				break
			}
			break
		case 1:
			//fixme: insert at the end
			switch (an) {
			case -1:
		} else {
				an = -3
				break
			case 3:			// natural
				an = -1
				break
			case 2:
				an = 3
				break
			}

			break
		case 3:
			switch (an) {
			case -1:
				an = 1
				break
			case 1:
				an = 1
				note.pit++
				break
			}
			break
		}
	}
	note.acc = d ? [an, d] : an
}

			// put the P: and/or Q: on a note or rest
// adjust the pitches according to the transposition(s)
function pit_adj() {
    var	i, p_v, s, sk, g,
	nv = voice_tb.length

	while (--nv >= 0) {
		p_v = voice_tb[nv]
		if (p_v.vtransp == undefined)
			continue	// no transposition in this voice
		if (p_v.key.k_transp) {
			sk = p_v.key
			key_transp(sk)
			sk.k_old_sf = sk.k_sf	// no natural
		} else {
			sk = null
		}
		s = p_v.sym
		while (s) {

			// search a transposing key signature
			if (!sk) {
			while (s.time == tim && s.ts_next
			 && !s.dur)
				s = s.ts_next
			if (e.part) {
				for (; s; s = s.next) {
					if (s.type == C.KEY
//				if (s.time != tim) {
					 && s.k_transp)
						break
				}
			}

			// transpose
			for (; s; s = s.next) {
//					// fixme: insert a part (?)
//				}
				s.part = e.part
				switch (s.type) {
				case C.GRACE:
					for (g = s.extra; g; g = g.next) {
						for (i = 0; i <= g.nhd; i++)
							note_transp(g, sk, g.notes[i])
					}
			}
					continue
				case C.NOTE:
					for (i = 0; i <= s.nhd; i++)
						note_transp(s, sk, s.notes[i])
					continue
				case C.KEY:
					if (sk)
						s.k_sf = sk.k_sf
			if (e.tempo) {
				lkvsym(e.tempo, s)
					key_transp(s)
					if (!s.k_transp) // end of transposition
						break
					sk = s
				lktsym(e.tempo, s)
				default:
					continue
				}
			}
				break
			}
		}
			sk = null
		}
	}
	}
} // pit_adj()
} // set_ctrl()

// get a abcm2ps/abcMIDI compatible transposition value as a base-40 interval
// The value may be
// - [+|-]<number of semitones>[s|f]
// - <note1>[<note2>]  % <note2> default is 'c'
function get_transp(param) {
	if (param[0] == '0')
︙
889
890
891
892
893
894
895
896

897
898
899
900
901
902
903
855
856
857
858
859
860
861

862
863
864
865
866
867
868
869







-
+







} // get_transp()

/* -- process a pseudo-comment (%% or I:) -- */
// (possible hook)
Abc.prototype.do_pscom = function(text) {
    var	h1, val, s, cmd, param, n, k, b

	cmd = text.match(/(\w|-)+/)
	cmd = text.match(/[^\s]+/)
	if (!cmd)
		return
	cmd = cmd[0];

	// ignore the command if the voice is ignored,
	// but not if %%score/%%staves!
	if (curvoice && curvoice.ignore) {
︙
953
954
955
956
957
958
959
960
961


962
963
964

965
966
967
968
969
970
971
972
973
974
975
976

977


978
979
980
981
982
983
984
985
986
987
988
989
990
991



992
993
994
995
996
997
998
919
920
921
922
923
924
925


926
927



928
929
930
931
932
933
934






935

936
937
938
939
940
941
942
943








944
945
946
947
948
949
950
951
952
953







-
-
+
+
-
-
-
+






-
-
-
-
-
-
+
-
+
+






-
-
-
-
-
-
-
-
+
+
+







		if (parse.state >= 2) {
			curvoice = voice_tb[0]
			s = new_block("mc_" + param)
			break
		}
		switch (param) {
		case "start":
			multicol = {
				maxy: 0,
			self.block_gen({
				subtype: "mc_start"
				lmarg: cfmt.leftmargin,
				rmarg: cfmt.rightmargin
			}
			})
			break
		case "new":
			if (!multicol) {
				syntax(1, "%%multicol new without start")
				break
			}
			if (posy > multicol.maxy)
				multicol.maxy = posy;
			cfmt.leftmargin = multicol.lmarg;
			cfmt.rightmargin = multicol.rmarg;
			img.chg = true;
			set_page();
			self.block_gen({
			posy = 0
				subtype: "mc_new"
			})
			break
		case "end":
			if (!multicol) {
				syntax(1, "%%multicol end without start")
				break
			}
			if (posy < multicol.maxy)
				posy = multicol.maxy;
			cfmt.leftmargin = multicol.lmarg;
			cfmt.rightmargin = multicol.rmarg;
			multicol = undefined;
			blk_flush();
			img.chg = true;
			set_page()
			self.block_gen({
				subtype: "mc_end"
			})
			break
		default:
			syntax(1, "Unknown keyword '$1' in %%multicol", param)
			break
		}
		return
	case "ottava":
︙
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198

1199
1200
1201
1202
1203
1204
1205
1132
1133
1134
1135
1136
1137
1138















1139
1140
1141
1142
1143
1144
1145
1146







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+







		case 0:
			cfmt.transp = 0
			// fall thru
		case 1:
			cfmt.transp = (cfmt.transp || 0) + val
			return
		}
		for (s = curvoice.last_sym; s; s = s.prev) {
			switch (s.type) {
			case C.NOTE:		// insert a key
				s = clone(curvoice.okey);
				s.k_old_sf = curvoice.ckey.k_sf;
				sym_link(s)
				break
			case C.KEY:
				break
			default:
				continue
			}
			break
		}
		curvoice.transp = val
		curvoice.tr_sco = val
		set_transp()
		return
	case "tune":
//fixme: to do
		return
	case "user":
		set_user(param)
︙
1317
1318
1319
1320
1321
1322
1323
1324

1325





1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341

1342

1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353



1354
1355
1356
1357
1358
1359

1360








1361

1362
1363

1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386


1387
1388
1389
1390
1391


1392


1393
1394
1395


1396
1397
1398


1399
1400

1401
1402

1403
1404
1405
1406
1407


1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424


1425
1426
1427

1428
1429
1430
1431
1432
1433
1434
1435



1436
1437

1438
1439
1440
1441
1442
1443




1444
1445

1446
1447

1448
1449
1450
1451
1452
1453
1454
1258
1259
1260
1261
1262
1263
1264

1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288

1289

1290
1291
1292
1293
1294

1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317

1318
1319

1320















1321

1322
1323
1324



1325
1326
1327



1328
1329
1330

1331
1332
1333


1334
1335
1336


1337
1338
1339

1340


1341





1342
1343

















1344
1345



1346








1347
1348
1349


1350






1351
1352
1353
1354


1355


1356
1357
1358
1359
1360
1361
1362
1363







-
+

+
+
+
+
+
















+
-
+
-





-




+
+
+






+

+
+
+
+
+
+
+
+
-
+

-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-



-
-
-
+
+

-
-
-

+
+
-
+
+

-
-
+
+

-
-
+
+

-
+
-
-
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
-
-
-
-
-
-
-
-
+
+
+
-
-
+
-
-
-
-
-
-
+
+
+
+
-
-
+
-
-
+







		}
		write_text(text, action)
		break
	}
}

/* -- generate a piece of tune -- */
function generate(in_mc) {
function generate() {
    var s, v, p_voice;

	if (a_dcn.length) {
		syntax(1, "Decoration without symbol")
		a_dcn = []
	}

	if (parse.tp) {
		syntax(1, "No end of tuplet")
		s = parse.tps
		if (s)
			delete s.tp
		delete parse.tp
	}

	if (vover) {
		syntax(1, "No end of voice overlay");
		get_vover(vover.bar ? '|' : ')')
	}

	voice_adj();
	sort_all()			/* define the time / vertical sequences */

	if (!tsfirst)
    if (tsfirst) {
		return
	if (user.anno_start)
		anno_start = a_start
	if (user.anno_stop)
		anno_stop = a_stop
	self.set_bar_num()
	pit_adj()

	if (info.P)
		tsfirst.parts = info.P	// for play

	if (parse.ctrl)
		set_ctrl()		// set control values

	// give the parser result to the application
	if (user.get_abcmodel)
		user.get_abcmodel(tsfirst, voice_tb, abc2svg.sym_name, info)

	if (user.img_out)		// if SVG generation
		self.output_music()
    } // (tsfirst)

	// finish the generation
	set_page()			// the page layout may have changed
	if (info.W)
		put_words(info.W)
	put_history()
	parse.state = 0			// file header
	blk_flush()			// (force end of block)

	if (tsfirst)		// if non void, keep tune data for upper layers
	if (tsfirst) {		// if non void, keep tune data for upper layers
		tunes.push([tsfirst, voice_tb, info, cfmt])

		tsfirst = null
	// if inside multicol, reset the parser
	if (!in_mc)
		return
	voice_tb = Object.create(voice_tb)
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v];
		p_voice.time = 0;
		p_voice.sym = p_voice.last_sym = null;
//		p_voice.st = cur_sy.voices[v].st;
//		p_voice.second = cur_sy.voices[v].second;
//		p_voice.clef.time = 0;
		delete p_voice.have_ly;
		p_voice.sls = [];
		p_voice.hy_st = 0;
		delete p_voice.bar_start
	}
	staves_found = 0			// (for compress/dup the voices)
}

// transpose a key
//fixme: transpose of the accidental list is not done
function key_transp(sk) {
	if (sk.k_a_acc || sk.k_none)		// same displayed key
function key_transp(s) {
	if (s.k_none)			// no key
		return
    var	d,
	k_b40 = sk.k_b40,
	n_b40 = (k_b40 + 200 + sk.k_transp) % 40

    var	n, a_acc,
	b40 = (s.k_b40 + 200 + curvoice.tr_sco) % 40,
	d = abc2svg.b40k[n_b40] - n_b40
	d = abc2svg.b40k[b40] - b40

	if (d) {
		if (sk.k_transp > 0)
			sk.k_transp += d
		if (curvoice.tr_sco > 0)
			curvoice.tr_sco -= d
		else
			sk.k_transp -= d
		n_b40 += d
			curvoice.tr_sco += d
		b40 += d
	}
	sk.k_b40 = n_b40
	s.k_b40 = b40

   var sf = abc2svg.b40sf[n_b40]
	s.k_sf = abc2svg.b40sf[b40]
	sk.k_old_sf = sk.k_sf
	sk.k_sf = sf
	sk.k_map = abc2svg.keys[sf + 7]	// map of the notes with accidentals
}


	// transpose the accidental list
/*
 * for transpose purpose, check if a pitch is already in the measure or
 * if it is tied from a previous note, and return the associated accidental
 */
function acc_same_pitch(s, pit) {
    var	i,
	time = s.time

	for (s = s.prev; s; s = s.prev) {
		switch (s.type) {
		case C.BAR:
			if (s.time < time)
				return //undefined // no same pitch
			while (1) {
				s = s.prev
				if (!s)
					return //undefined
	if (!s.k_a_acc)
		return
				if (s.type == C.NOTE) {
					if (s.time + s.dur == time)
						break
	a_acc = []
					return //undefined
				}
				if (s.time < time)
					return //undefined
			}
			for (i = 0; i <= s.nhd; i++) {
				if (s.notes[i].pit == pit
				 && s.notes[i].tie_ty)
	for (n = 0; n < s.k_a_acc.length; n++) {
		b40 = abc2svg.pab40(s.k_a_acc[n].pit, s.k_a_acc[n].acc)
			+ curvoice.tr_sco
					return s.notes[i].acc
			}

			return //undefined
		case C.NOTE:
			for (i = 0; i <= s.nhd; i++) {
				if (s.notes[i].pit == pit)
					return s.notes[i].acc
			}
		a_acc[n] = {
			pit: abc2svg.b40p(b40),
			acc: abc2svg.b40a(b40) || 3
		}
			break
		}
	}
	}
	return //undefined
	s.k_a_acc = a_acc
}

/* -- get staves definition (%%staves / %%score) -- */
function get_staves(cmd, parm) {
    var	s, p_voice, p_voice2, i, flags, v, vid, a_vf,
	st, range,
	nv = voice_tb.length,
︙
1700
1701
1702
1703
1704
1705
1706
1707

1708
1709
1710
1711
1712
1713
1714




1715
1716
1717
1718
1719
1720
1721
1722

1723
1724
1725
1726
1727




















1728

1729
1730
1731
1732
1733
1734
1735
1609
1610
1611
1612
1613
1614
1615

1616

1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660

1661
1662
1663
1664
1665
1666
1667
1668







-
+
-






+
+
+
+








+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+








		voice_tb.push(p_voice)
		return p_voice
	} // clone_voice()

/* -- get a voice overlay -- */
function get_vover(type) {
    var	p_voice2, p_voice3, range, s, time, v, v2, v3,
    var	p_voice2, p_voice3, range, s, time, v, v2, v3, s2
	line = parse.line

	/* treat the end of overlay */
	if (type == '|'
	 || type == ')')  {
		if (!curvoice.last_note) {
			syntax(1, errs.nonote_vo)
			if (vover) {
				curvoice = vover.p_voice
				vover = null
			}
			return
		}
		curvoice.last_note.beam_end = true
		if (!vover) {
			syntax(1, "Erroneous end of voice overlay")
			return
		}
		if (curvoice.time != vover.p_voice.time) {
		    if (!curvoice.ignore)
			syntax(1, "Wrong duration in voice overlay");
			if (curvoice.time > vover.p_voice.time)
				vover.p_voice.time = curvoice.time
		}
		curvoice.acc = []		// no accidental anymore

		// if the last symbols are spaces, move them to the main voice
		p_voice2 = vover.p_voice	// main voice
		s = curvoice.last_sym
		if (s.type == C.SPACE && p_voice2.last_sym.type != C.SPACE) {
			s.p_v = p_voice2
			s.v = s.p_v.v
			while (s.prev.type == C.SPACE) {
				s = s.prev
				s.p_v = p_voice2
				s.v = s.p_v.v
			}
			s2 = s.prev
			s2.next = null
			s.prev = p_voice2.last_sym
			s.prev.next = s
			p_voice2.last_sym = curvoice.last_sym
			curvoice.last_sym = s2
		}

		curvoice = vover.p_voice;
		curvoice = p_voice2
		vover = null
		return
	}

	/* treat the full overlay start */
	if (type == '(') {
		if (vover) {
︙
1752
1753
1754
1755
1756
1757
1758

1759

1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770

1771
1772
1773
1774
1775
1776
1777



1778
1779
1780
1781
1782
1783
1784
1785
1786

1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819




1820
1821
1822
1823
1824
1825
1826
1827

1828

1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839

1840

1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858

1859
1860
1861


1862
1863
1864
1865
1866
1867
1868
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724

1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736












1737
1738
1739
1740
1741
1742




1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755

1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768

1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786

1787
1788
1789

1790
1791
1792
1793
1794
1795
1796
1797
1798







+

+











+







+
+
+








-
+











-
-
-
-
-
-
-
-
-
-
-
-






-
-
-
-
+
+
+
+








+
-
+











+
-
+

















-
+


-
+
+







	curvoice.last_note.beam_end = true;
	p_voice2 = curvoice.voice_down
	if (!p_voice2) {
		p_voice2 = clone_voice(curvoice.id + 'o');
		curvoice.voice_down = p_voice2;
		p_voice2.time = 0;
		p_voice2.second = true;
		p_voice2.last_note = null
		v2 = p_voice2.v;
	    if (par_sy.voices[curvoice.v]) {	// if voice in the staff system
		par_sy.voices[v2] = {
			st: curvoice.st,
			second: true
		}
		range = par_sy.voices[curvoice.v].range
		for (v = 0; v < par_sy.voices.length; v++) {
			if (par_sy.voices[v]
			 && par_sy.voices[v].range > range)
				par_sy.voices[v].range++
		}
		par_sy.voices[v2].range = range + 1
	    }
	}
	p_voice2.ulen = curvoice.ulen
	p_voice2.dur_fact = curvoice.dur_fact
	p_voice2.acc = []			// no accidental

	if (!vover) {				/* first '&' in a measure */
		time = p_voice2.time
	    if (curvoice.ignore)
		s = null
	    else
		for (s = curvoice.last_sym; /*s*/; s = s.prev) {
			if (s.type == C.BAR
			 || s.time <= time)	/* (if start of tune) */
				break
		}
		vover = {
			bar: (s && s.bar_type) ? s.bar_type : '|',
			p_voice: curvoice,
			time: s.time
			time: s ? s.time : curvoice.time
		}
	} else {
		if (curvoice != vover.p_voice
		 && curvoice.time != vover.p_voice.time) {
			syntax(1, "Wrong duration in voice overlay")
			if (curvoice.time > vover.p_voice.time)
				vover.p_voice.time = curvoice.time
		}
	}
	p_voice2.time = vover.time;
	curvoice = p_voice2

	// add a bar at start of the measure overlay
	// (needed for sort_all() in case of spaces - 'y')
	if (vover.bar && vover.time) {
		sym_link({
			type: C.BAR,
			bar_type: vover.bar,
			invis: 1,
			dur: 0,
			multi: 0
		})
	}
}

// check if a clef, key or time signature may go at start of the current voice
function is_voice_sig() {
	var s

	if (!curvoice.last_sym)
		return true
	if (curvoice.time)
		return false
	if (curvoice.time)
		return false
	if (!curvoice.last_sym)
		return true
	for (s = curvoice.last_sym; s; s = s.prev)
		if (w_tb[s.type])
			return false
	return true
}

// treat a clef found in the tune body
function get_clef(s) {
	if (!curvoice.time		// (force a clef when new voice)
	if (is_voice_sig()) {
	 && is_voice_sig()) {
		curvoice.clef = s
		s.fmt = cfmt
		return
	}

	// clef change
	sym_link(s);
	s.clef_small = true

	// move the clef before a (not right repeat) bar
    var	s2 = s.prev
	if (!s.invis			// if not 'K: clef=none'
	if (s2 && s2.type == C.BAR
	 && s2 && s2.type == C.BAR
	 && s2.bar_type[0] != ':') {
		s.next = s2
		s.prev = s2.prev
		if (s.prev)
			s.prev.next = s
		s2.prev = s
		s2.next = null
		curvoice.last_sym = s2
		if (s.soln) {
			delete s.soln
			curvoice.eoln = true
		}
	}
}

// treat K: (kp = key signature + parameters)
function get_key(parm) {
	var	v, p_voice, s, transp, sndtran,
    var	v, p_voice, transp, sndtran, tr_p, nt,
//		[s_key, a] = new_key(parm)	// KO with nodejs
		a = new_key(parm),
		s_key = a[0];
		s_key = a[0],
		s = s_key

	a = a[1]

	if (parse.state == 1) {		// in tune header (first K:)
		if (s_key.k_sf == undefined && !s_key.k_a_acc) { // empty K:
			s_key.k_sf = 0;
			s_key.k_none = true
︙
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890




1891
1892

1893
1894
1895

1896
1897
1898
1899
1900
1901
1902
1903


1904
1905
1906


1907
1908
1909


1910
1911
1912
1913
1914
1915
1916
1917
1918




1919
1920

1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1808
1809
1810
1811
1812
1813
1814






1815
1816
1817
1818


1819



1820








1821
1822



1823
1824



1825
1826
1827
1828







1829
1830
1831
1832
1833

1834
1835
1836
1837
1838
1839
1840
1841
1842
1843

1844
1845
1846
1847
1848
1849
1850







-
-
-
-
-
-
+
+
+
+
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
+
+
-
-
-
+
+
-
-
-
+
+


-
-
-
-
-
-
-
+
+
+
+

-
+









-







		if (!glovar.ulen)
			glovar.ulen = C.BLEN / 8;
		if (a.length) {
			memo_kv_parm('*', a)
			a = ''
		}
		goto_tune()
		parse.state = 3			// in tune body
	}

	set_kv_parm(a)

	if (!curvoice.ckey.k_bagpipe && !curvoice.ckey.k_drum
	} else {
		set_kv_parm(a)
	}

	 && (cfmt.transp != undefined
	  || curvoice.transp != undefined
	tr_p = curvoice.tr_p			// transposition change 1=sco, 2=snd
	  || curvoice.shift != undefined))
	    transp = (cfmt.transp || 0) +
		(curvoice.transp || 0) +
	curvoice.tr_p = 0
		(curvoice.shift || 0)
	if (curvoice.sndtran != undefined
	 || curvoice.sndsh != undefined)
		sndtran = (curvoice.sndtran || 0) +
			(curvoice.sndsh || 0)

	if (s_key.k_sf == undefined) {
		if (!s_key.k_a_acc
	if (s.k_sf == undefined) {		// if no key
		if (!s.k_a_acc			// if no accidental list
		 && transp == undefined) {
			if (sndtran == undefined)
				return		// not a key signature
		 && !(tr_p & 1))		// and no score transposition
			return			// not a key signature
			s_key.invis = true	// play only
		}
		s_key.k_sf = curvoice.okey.k_sf
		s_key.k_sf = curvoice.okey.k_sf	// set the same key
		s_key.k_b40 = curvoice.okey.k_b40
	}

	curvoice.okey = clone(s_key)
	if (transp != undefined) {
		curvoice.vtransp = transp;
		s_key.k_transp = transp
	}
	if (sndtran != undefined)
		s_key.k_sndtran = sndtran

	if (curvoice.tr_sco)			// if transpose score
		key_transp(s)
	curvoice.okey = clone(s_key)

	s_key.k_old_sf = curvoice.ckey.k_sf;	// memorize the key changes
	s_key.k_old_sf = curvoice.ckey.k_sf	// memorize the key changes

	if ((!s_key.k_a_acc || !s_key.k_a_acc.length)
	 && !s_key.k_sf && !s_key.k_old_sf)
		s_key.invis = true		// don't display the key signature

	if (!s_key.k_b40)
		s_key.k_b40 = curvoice.ckey.k_b40

	curvoice.ckey = s_key

	if (is_voice_sig()) {
		s_key.fmt = cfmt
		curvoice.key = clone(s_key)
		if (s_key.k_none)
			curvoice.key.k_sf = 0
	} else {
		sym_link(s_key)			// (don't move the key)
︙
1951
1952
1953
1954
1955
1956
1957

1958
1959
1960
1961
1962
1963
1964
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878







+







		if (!p_voice.time) {		// if no symbol yet
			p_voice.id = id
			delete p_voice.init	// reload the global attributes
			if (cfmt.transp	// != undefined
			 && parse.state >= 2) {
				p_v_sav = curvoice;
				curvoice = p_voice;
				curvoice.tr_sco = cfmt.transp
				set_transp();
				curvoice = p_v_sav
			}
			return p_voice		// default voice
		}
	}
	for (v = 0; v < n; v++) {
︙
2100
2101
2102
2103
2104
2105
2106




2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121



2122
2123
2124
2125
2126
2127
2128
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049







+
+
+
+















+
+
+







			bol: parse.iend
		}
		if (parse.file[curvoice.clone.bol - 1] != ']')
			curvoice.clone.bol++	// start of new line
	}

	set_kv_parm(a)
	if (curvoice.tr_p) {
		curvoice.tr_p = 0
		set_transp()
	}

	v = curvoice.v
	if (curvoice.new) {			// if new voice
		delete curvoice.new
		if (staves_found < 0) {		// if no %%score/%%staves
			curvoice.st = curvoice.cst = ++nstaff;
			par_sy.nstaff = nstaff;
			par_sy.voices[v] = {
				st: nstaff,
				range: v
			}
			par_sy.staves[nstaff] = {
				stafflines: curvoice.stafflines || "|||||",
				staffscale: 1
			}
		} else if (!par_sy.voices[v]) {
			curvoice.ignore = 1	// voice not declared in %%staves
			return
		}
	}

	if (!curvoice.filtered
	 && par_sy.voices[v]
	 && (parse.voice_opts
	  || parse.tune_v_opts)) {
︙
2140
2141
2142
2143
2144
2145
2146


2147
2148
2149
2150
2151
2152
2153
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076







+
+







	write_heading();
	if (glovar.new_nbar) {
		gene.nbar = glovar.new_nbar	// measure numbering
		glovar.new_nbar = 0
	} else {
		gene.nbar = 1
	}

	parse.state = 3				// in tune body

	// if no voice yet, create the default voice
	if (!voice_tb.length) {
		get_voice("1");
		curvoice.clef.istart = curvoice.key.istart;
		curvoice.clef.iend = curvoice.key.iend;
//		nstaff = 0;
︙
Added dist_bin.






























































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
COPYING
COPYING.LESSER
MIDI-1.js
Scc1t2/
abc2svg-1.js
abc2svg.ttf
abc2svg.woff
abcdoc-1.js
abcjsc
abcmjs
abcnode
abcqjs
abctopdf
abcweb1-1.js
abcweb-1.js
ambitus-1.js
break-1.js
capo-1.js
chordnames-1.js
clair-1.js
clip-1.js
cmdline.js
combine-1.js
diag-1.js
edit-1.css
edit-1.js
edit-1.xhtml
edit-de.js
edit-en.js
edit-fr.js
edit-it.js
edit-pt_BR.js
equalbars-1.js
err-de.js
err-en.js
err-fr.js
err-it.js
err-pt_BR.js
gamelan-1.js
grid2-1.js
grid-1.js
grid3-1.js
jazzchord-1.js
jianpu-1.js
mdnn-1.js
nns-1.js
page-1.js
pedline-1.js
perc-1.js
psvg-1.js
roman-1.js
snd-1.js
soloffs-1.js
sth-1.js
strtab-1.js
temper-1.js
tropt-1.js
toabc.js
tohtml.js
tomei.js
tonotes.js
toparam.js
Changes to editor/edit.css.
︙
96
97
98
99
100
101
102

103
104
96
97
98
99
100
101
102
103
104
105







+


	#dleft, #ctxMenu, .popup { display: none }
	body {
		margin:0; padding:0; border:0;
		background-color: white;
	}
	#dright { margin:0; padding:0; border:0 }
	.newpage { page-break-before: always }
	.newpage:first-of-type { page-break-before: avoid }
	.nobrk { page-break-inside: avoid }
}
Changes to editor/edit.js.
︙
290
291
292
293
294
295
296


297

298
299
300
301
302
303
304
290
291
292
293
294
295
296
297
298

299
300
301
302
303
304
305
306







+
+
-
+







// select a source ABC element on error
function gotoabc(l, c) {
    var	s = elt_ref.source

	selsrc(0)
	if (l >= 0)
		c = soffs(l, Number(c))
	s.blur()			// unfocus
	s.setSelectionRange(c, c)	// just set the cursor (no range)
	s.focus();
	s.focus()			// should scroll the textarea
	s.setSelectionRange(c, syms[c] ? syms[c].iend : c + 1)
}

// click in the target
function selsvg(evt) {
    var	v,
	cl = evt.target.getAttribute('class')
︙
319
320
321
322
323
324
325

326

327
328
329
330

331
332
333


334
335
336
337
338
339
340
321
322
323
324
325
326
327
328

329




330


331
332
333
334
335
336
337
338
339
340







+
-
+
-
-
-
-
+
-
-

+
+







		play.stop = -1;
		play.abcplay.stop()
		return
	}

	// highlight the clicked element or clear the selection start
	s = elt_ref.source;
	s.blur();
	if (cl && cl.substr(0, 4) == 'abcr') {
	v = cl && cl.substr(0, 4) == 'abcr' ? Number(cl.slice(6, -1)) : 0
		v = Number(cl.slice(6, -1));
		s.setSelectionRange(v, syms[v].iend)
	} else {
		s.setSelectionRange(0, 0)
	s.setSelectionRange(v, v)	// just a cursor
	}
	s.blur();
	s.focus()
	if (v)
		s.setSelectionRange(v, syms[v].iend)
}

// set/clear a selection
function setsel(idx, v) {
    var i, elts, s,
	old_v = selx[idx];

︙
353
354
355
356
357
358
359
360
361
362





363
364
365
366
367
368





369
370
371
372
373
374
375











376
377
378
379
380
381
382
353
354
355
356
357
358
359



360
361
362
363
364

365




366
367
368
369
370







371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388







-
-
-
+
+
+
+
+
-

-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+







			elts[i].style.fillOpacity = 0.4
	}

	selx[idx] = v
}

function do_scroll(elt) {
    var	r,
	dr = elt_ref.target.parentElement,		// <div> 'dright'
	drh = dr.getBoundingClientRect().height,	// height of the view
    var	x = 0,
	y = 0,
	b = elt.getBoundingClientRect(),		// box of the rectangle
	d = elt_ref.target.parentElement,		// <div> 'dright'
	r = elt.parentNode.getBoundingClientRect()	// box of the svg container
	ty = elt_ref.target.getBoundingClientRect().y	// y offset of <div> 'target'

		elt = elt.parentNode;
	r = elt.getBoundingClientRect()
// upper -> top, lower -> bottom
	if (r.y < 0)				// y offset of the svg container
	if (b.x < d.offsetLeft		// x offset of the rectangle
	 || b.x + b.width > d.offsetLeft + d.clientWidth * .7)
		x = b.x - d.offsetLeft - d.clientWidth * .3

	if (r.y < d.offsetTop		// y offset of the svg container
		dr.scrollTo(0, r.y - ty)
	else if (r.y + r.height > drh)
		dr.scrollTo(0, r.y - ty - drh + r.height)
// in the middle
//	if (r.y < 0				// y offset of the svg container
//	 || r.y + r.height > drh)
//		dr.scrollTo(0, r.y - ty - drh / 2 + r.height)
	 || r.y + r.height > d.offsetTop + d.clientHeight * .7)
		y = r.y - d.offsetTop - d.clientHeight * .3

	if (x || y)
//		d.scrollBy(x, y)
		d.scrollBy({
			top: y,
			left: x,
			behavior: (x < 0 || y) ? 'instant' : 'smooth'
				// 'smooth', 'instant' or 'auto' (default)
		})
}

// source text selection callback
function seltxt(evt) {
    var	s, elts,
	e = 0,
	elt = elt_ref.source,
︙
514
515
516
517
518
519
520


521
522
523
524
525
526
527
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535







+
+







// start playing
//	-1: All (removed)
//	0: Tune
//	1: Selection
//	2: Loop
//	3: Continue
function play_tune(what) {
	if (!abc)
		return			// no generation yet
    var	i, si, ei, elt,
	C = abc2svg.C,
	tunes = abc.tunes

	if (play.playing) {
		if (!play.stop) {
			play.stop = -1;
︙
809
810
811
812
813
814
815
816
817
818
819
820
821

822

823
824






825

826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842



843
844
845
846
847
848
849
850
851
817
818
819
820
821
822
823



824
825
826
827

828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843

844
845
846
847
848
849
850
851
852


853
854
855


856
857
858
859
860
861
862







-
-
-



+
-
+


+
+
+
+
+
+

+





-









-
-
+
+
+
-
-







		// display the play menu
		// This function is called on non left click
		function show_menu(evt) {
		    var	x, y,
			elt = evt.target,
			cl = elt.getAttribute('class')

//			evt.stopImmediatePropagation();
			evt.preventDefault()

			// if right click on an element, select it
			if (cl && cl.substr(0, 4) == 'abcr') {
				setsel(1, Number(cl.slice(6, -1)))
				evt.preventDefault()
				return false
				return
			}

			// if the play menu is already active
			// display the default context menu
			ctxMenu = document.getElementById("ctxMenu");
			if (ctxMenu.style.display == "block")
				return

			// otherwise, display the play menu
			evt.preventDefault()
			play.click = {	// keep the click references for 'play tune'
				svg: elt,
				Y: evt.pageY
			}

			ctxMenu = document.getElementById("ctxMenu");
			ctxMenu.style.display = "block";
			x = evt.pageX - elt_ref.target.parentNode.offsetLeft
					+ elt_ref.target.parentNode.scrollLeft;
			y = evt.pageY + elt_ref.target.parentNode.scrollTop;
			ctxMenu.style.left = (x - 30) + "px";
			ctxMenu.style.top = (y - 10) + "px"
		} // showmenu

		e = elt_ref.target
		e.onauxclick = show_menu	// right or middle buttons
		e.oncontextmenu = function(ev) {
//		e.onauxclick = show_menu	// right or middle buttons (KO in Safari)
		e.ondblclick = show_menu	// double click
		e.oncontextmenu = show_menu
			ev.preventDefault()
		}
	}
	set_pref()	// set the preferences from local storage
}

// drag and drop
function drag_enter(evt) {
	evt.stopImmediatePropagation();
︙
Changes to editor/edit.xhtml.

1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20
1
2
3
4
5

6
7
8
9
10

11

12
13
14
15
16
17
18
19
+




-





-

-
+







<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link href="edit-@MAJOR@.css" rel="stylesheet" type="text/css"/>
<script src="edit-@MAJOR@.js" type="text/javascript" async="true"></script>
<script src="abc2svg-@MAJOR@.js" type="text/javascript" async="true"></script>
<script src="pref.js" type="text/javascript" async="true"></script>
<title>ABC edit with abc2svg</title>

</head>
<body ontouchstart="" bgcolor="#faf0e6">
<body ontouchstart="" bgcolor="#faf0e6" spellcheck="false">

<!-- left -->
<div id="dleft">

<!-- menu bar -->
    <ul id="nav">
	<li class="dropbutton"><label id="f">File</label>
︙
141
142
143
144
145
146
147
148

149
150
151
152
153
154
155
156
157
158
159
160
140
141
142
143
144
145
146

147
148
149
150
151
152
153
154
155
156
157
158
159







-
+












</ul>
</div>

<!-- about popup window -->
<div id="about" class="popup" style="width: 350px">
<div class="close" onclick="popshow('about')">x</div>
<p id="abc2svg">abc2svg</p>
<p>Copyright &#x00A9; 2016-2021 <a
<p>Copyright &#x00A9; 2016-2022 <a
href="http://moinejf.free.fr">Jean-François Moine</a></p>
</div>

<!-- javascript must be enabled -->
<noscript>
<div id="okjs" class="popup" style="width:50%;background:#ff8080;visibility:visible">
Please enable Javascript.
</div>
</noscript>

</body>
</html>
Changes to err-de.js.
︙
88
89
90
91
92
93
94

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110


111
112
113
114
115
116
117
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120







+
















+
+







	"Error loading the module $1": "Error loading the module $1",
    "Incorrect 1st value in %%repeat": "erster Wert fehlerhaft in %%repeat",
    "Incorrect 2nd value in %%repeat": "zweiter Wert fehlerhaft in %%repeat",
    "Invalid 'r' in tuplet": "'r' fehlerhaft in Tuplet",
    "Invalid decoration '$1'": "fehlerhafte Dekoration '$1'",
    "Invalid note duration $1": "Fehlerhafte Notendekoration '$1'",
	"K:P is deprecated": "K:P is deprecated",
	"Key with double sharps/flats": "Key with double sharps/flats",
    "Lack of ']'": "']' fehlt",
	"Lack of bar after multi-measure rest":
				"Lack of bar after multi-measure rest",
	"Lack of ending slur(s)": "Lack of ending slur(s)",
	"Lack of starting [, ! or \" in U: / %%user":
				"[, ! or \" fehlt in U: / %%user",
	"Line split problem - adjust maxshrink and/or breaklimit":
		"Probleme beim Aufteilen der Zeile - justiere maxshrink und/oder breaklimit",
    "Line too much shrunk $1 $2 $3": "Zeile zu stark geschrumpft $1 $2 $3",
    "Line underfull ($1pt of $2pt)": "Zeile nicht voll genug ($1pt of $2pt)",
	"Microtone transposition not coded":
				"Microtone transposition not coded",
    "Misplaced '$1' in %%score": "'$1' fehlerhaft platziert in %%score",
    "Misplaced dot": "fehlerhaft platzierter Punkt",
	"Mix of old and new transposition syntaxes":
				"Mix von alter und neuer Transponierungs-syntax",
	"multi-measure rest, but no measure!":
			"multi-measure rest, but no measure!",
    "No $1 after %%$2": "kein $1 nach %%$2",
    "No </defs> in %%beginsvg sequence": "kein </defs> in %%beginsvg squenz",
    "No </style> in %%beginsvg sequence": "kein </style> in %%beginsvg sequenz",
    "No accidental after 'exp'": "kein Vorzeichen nach 'exp'",
    "No end of decoration": "kein Ende der Dekoration",
	"No end of grace note sequence": "kein Ende der Vorschlagnoten",
    "No end of chord symbol/annotation": "kein Ende des Akkordsymbols",
︙
143
144
145
146
147
148
149

150
151
152
153
154
155
156
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160







+







	"s: without music": "s: ohne Musik",
	"Too many elements in symbol line": "zu viele elemente in einer Symbolzeile",
	"Too many include levels": "Verschachtelung für include zu tief",
	"There cannot be chord symbols on measure bars":
			"There cannot be chord symbols on measure bars",
    "Too many ties": "zu viele Haltebogen",
    "Too many words in lyric line": "zu viele Worte in Liedtextzeile",
	"triple sharp/flat": "triple sharp/flat",
    "Unknown bagpipe-like key": "unbekante Dudelsackartige Tonart",
    "Unknown clef '$1'": "unbekannter Schlüssel '$1'",
    "Unknown decoration '$1'": "unbekannte Dekoration '$1'",
	"Unknown font $1": "unbekannte Schriftart '$1'",
    "Unknown glyph: '$1'": "Unbekanntes Schriftsybol '$1'",
	"Unknown keyword '$1' in %%multicol": "unbekanntes Schlüsselwort '$1' in %%multicol",
	"Unsecure code": "Unsicherer Code",
︙
Changes to err-fr.js.
︙
94
95
96
97
98
99
100

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116


117
118
119
120
121
122
123
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126







+
















+
+







	"Error loading the module $1": "Erreur de chargement du module $1",
	"Incorrect 1st value in %%repeat": "Première valeur incorrecte dans %%repeat",
	"Incorrect 2nd value in %%repeat": "Deuxième valeur incorrecte dans %%repeat",
	"Invalid 'r' in tuplet": "'r' invalide dans le tuplet",
	"Invalid decoration '$1'": "Décoration '$1' invalide",
	"Invalid note duration $1": "Durée de note $1 invalide",
	"K:P is deprecated": "K:P est déprécié",
	"Key with double sharps/flats": "Armature avec doubles dièses/bémols",
	"Lack of ']'": "']' manquant",
	"Lack of bar after multi-measure rest":
		"Manque de barre de mesure après silence multiple",
	"Lack of ending slur(s)": "Pas de fin de liaison(s)",
	'Lack of starting [, ! or " in U: / %%user':
				'U: / %%user ne commence pas par [, ! ou "',
	"Line split problem - adjust maxshrink and/or breaklimit":
		"Problème de découpe en lignes - ajuster maxshrink et/ou breaklimit",
	"Line too much shrunk $1 $2 $3": "Ligne trop compressée $1 $2 $3",
	"Line underfull ($1pt of $2pt)": "Trop d'espacement ($1pt of $2pt)",
	"Microtone transposition not coded":
				"Transposition de microtonalité pas codée",
	"Misplaced '$1' in %%score": "'$1' mal placé dans %%score",
	"Misplaced dot": "Point mal placé",
	"Mix of old and new transposition syntaxes":
			"Mélange des syntaxes de transposition ancienne et nouvelle",
	"multi-measure rest, but no measure!":
			"silence multi-mesure, mais pas de mesure!",
	"No $1 after %%$2": "Pas de $1 après %%$2",
	"No </defs> in %%beginsvg sequence":
				"Pas de </defs> dans séquence %%beginsvg",
	"No </style> in %%beginsvg sequence":
				"Pas de </style> dans séquence %%beginsvg",
	"No accidental after 'exp'": "Pas d'altération après 'exp'",
	"No end of decoration": "Pas de fin de décoration",
︙
155
156
157
158
159
160
161

162
163
164
165
166
167
168
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172







+







	"s: without music": "s: sans musique",
	"There cannot be chord symbols on measure bars":
		"On ne peut pas mettre d'indication d'accord sur une barre de mesure",
	"Too many elements in symbol line": "Trop d'éléments dans ligne s:",
	"Too many include levels": "Trop de niveaux d'inclusion de fichier",
	"Too many ties": "Trop de liaisons de prolongation",
	"Too many words in lyric line": "Trop de mots dans la ligne de paroles",
	"triple sharp/flat": "triple dièse/bémol",
	"Unknown bagpipe-like key": "Armature de cornemuse inconnue",
	"Unknown clef '$1'": "Clé '$1' inconnue",
	"Unknown decoration '$1'": "Décoration '$1' inconnue",
	"Unknown font $1": "Police $1 inconnue",
	"Unknown glyph: '$1'": "Glyph '$1' inconnu",
	"Unknown keyword '$1' in %%multicol": "Mot clé '$1' inconnu dans %%multicol",
	"Unsecure code": "Code dangereux",
︙
Changes to err-it.js.
︙
94
95
96
97
98
99
100

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116


117
118
119
120
121
122
123
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126







+
















+
+







	"Error loading the module $1": "Error loading the module $1",
	"Incorrect 1st value in %%repeat": "Primo valore in %%repeat errato",
	"Incorrect 2nd value in %%repeat": "Secondo valore in %%repeat errato",
	"Invalid 'r' in tuplet": "'r' non valido in gruppo irregolare",
	"Invalid decoration '$1'": "Decorazione '$1' non valida",
	"Invalid note duration $1": "Durée de note $1 non valida",
	"K:P is deprecated": "K:P is deprecated",
	"Key with double sharps/flats": "Key with double sharps/flats",
	"Lack of ']'": "']' mancante",
	"Lack of bar after multi-measure rest":
				"Lack of bar after multi-measure rest",
	"Lack of ending slur(s)": "Lack of ending slur(s)",
	'Lack of starting [, ! or " in U: / %%user':
				'U: / %%user non inizia con [, ! o "',
	"Line split problem - adjust maxshrink and/or breaklimit":
		"Problema di linea divisa - modificare maxshrink e/o breaklimit",
	"Line too much shrunk $1 $2 $3": "Linea troppo compressa $1 $2 $3",
	"Line underfull ($1pt of $2pt)": "Linea troppo vuota ($1pt di $2pt)",
	"Microtone transposition not coded":
				"Microtone transposition not coded",
	"Misplaced '$1' in %%score": "'$1' in posizione errata in %%score",
	"Misplaced dot": "Punto in posizione errata",
	"Mix of old and new transposition syntaxes":
			"Sintassi di trasposizione vecchia e nuova allo stesso tempo",
	"multi-measure rest, but no measure!":
			"multi-measure rest, but no measure!",
	"No $1 after %%$2": "Manca $1 dopo %%$2",
	"No </defs> in %%beginsvg sequence":
				"Manca </defs> nella sequenza %%beginsvg",
	"No </style> in %%beginsvg sequence":
				"manca </style> nella sequenza %%beginsvg",
	"No accidental after 'exp'": "Manca l'alterazione dopo 'exp'",
	"No end of chord symbol/annotation": "Accordo di accompagnamento non terminato",
︙
154
155
156
157
158
159
160

161
162
163
164
165
166
167
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171







+







	"s: without music": "s: senza musica",
	"There cannot be chord symbols on measure bars":
			"There cannot be chord symbols on measure bars",
	"Too many elements in symbol line": "Troppi elementi nella linea s:",
	"Too many include levels": "Troppi livelli di inclusione di file",
	"Too many ties": "Troppi simboli di legato",
	"Too many words in lyric line": "Troppe parole per la linea di musica",
	"triple sharp/flat": "triple sharp/flat",
	"Unknown bagpipe-like key": "Chiave per cornamusa sconosciuta",
	"Unknown clef '$1'": "Chiave '$1' sconosciuta",
	"Unknown decoration '$1'": "Decorazione '$1' sconosciuta",
	"Unknown font $1": "Font $1 sconosciuto",
	"Unknown glyph: '$1'": "Glifo '$1' sconosciuto",
	"Unknown keyword '$1' in %%multicol": "Keyword '$1' sconosciuta in %%multicol",
        "Unsecure code": "Codice pericoloso",
︙
Changes to err-pt_BR.js.
1
2
3
4
5

6
7
8
9
10
11
12
1
2
3
4

5
6
7
8
9
10
11
12




-
+







//JS abc2svg - Brazilian Portuguese translation of the error codes:
//			Tradução dos códigos de erro para português brasileiro
// Adicione este arquivo na linha de comando para obter as
// mensagens de erro em português do Brasil.
// (Hudson Lacerda - 11/dez/2016 - 23/set/2021)
// (Hudson Lacerda - 11/dez/2016 - 23/set/2021 - 02/nov/2022)

user.textrans = {
	"+: lyric without music": "+: palavras sem música",
	"+: symbol line without music": "+: linha de símbolos sem música",
	"+: without previous info field": "+: continuação sem campo anterior",
	"'{' in grace note": "'{' em notinha ornamental",
	"'}', ')' or ']' missing in %%score":
︙
92
93
94
95
96
97
98

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114


115
116
117
118
119
120
121
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124







+
















+
+







	"Error loading the module $1": "Error loading the module $1",
	"Incorrect 1st value in %%repeat": "Primeiro valor incorreto em %%repeat",
	"Incorrect 2nd value in %%repeat": "Segundo valor incorreto em %%repeat",
	"Invalid 'r' in tuplet": "'r' inválido em quiáltera",
	"Invalid decoration '$1'": "Decoração '$1' inválida",
	"Invalid note duration $1": "Duração de nota $1 inválida",
	"K:P is deprecated": "K:P é obsoleto",
	"Key with double sharps/flats": "Tonalidade com sustenidos/bemóis duplos",
	"Lack of ']'": "']' faltando",
	"Lack of bar after multi-measure rest":
				"Falta barra de compasso após pausa de múltiplos compassos",
	"Lack of ending slur(s)": "Falta fim de ligadura(s)",
	'Lack of starting [, ! or " in U: / %%user':
				'U: / %%user não começou com [, ! ou "',
	"Line split problem - adjust maxshrink and/or breaklimit":
		"Problema de quebra de linha - ajuste maxshrink e/ou breaklimit",
	"Line too much shrunk $1 $2 $3": "Linha muito encolhida $1 $2 $3",
	"Line underfull ($1pt of $2pt)": "Linha muito espaçada ($1pt de $2pt)",
	"Microtone transposition not coded":
				"A transposição de microtons não está codificada",
	"Misplaced '$1' in %%score": "'$1' deslocado em %%score",
	"Misplaced dot": "Ponto fora de lugar",
	"Mix of old and new transposition syntaxes":
				"Mistura de sintaxes de transposição antiga e nova",
	"multi-measure rest, but no measure!":
		"Pausa de múltiplos compassos, porém o compasso não foi definido!",
	"No $1 after %%$2": "Falta $1 após %%$2",
	"No </defs> in %%beginsvg sequence":
				"Falta </defs> em bloco %%beginsvg",
	"No </style> in %%beginsvg sequence":
				"Falta </style> em bloco %%beginsvg",
	"No accidental after 'exp'": "Falta acidente após 'exp'",
	"No end of decoration": "Decoração sem término",
︙
152
153
154
155
156
157
158

159
160
161
162
163
164
165
166
167
168
169
170


171
172
173

174
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172


173
174
175
176

177
178







+










-
-
+
+


-
+

	"s: without music": "s: sem música",
	"There cannot be chord symbols on measure bars":
			"Não pode haver cifra de acorde em barra de compasso",
	"Too many elements in symbol line": "Excesso de elementos na linha de símbolos",
	"Too many include levels": "Excessivos níveis de inclusão de arquivos",
	"Too many ties": "Excesso de ligaduras de prolongamento",
	"Too many words in lyric line": "Excesso de palavras",
	"triple sharp/flat": "sustenido/bemol triplo",
	"Unknown bagpipe-like key": "Armadura desconhecida para gaita de foles",
	"Unknown clef '$1'": "Clave '$1' desconhecida",
	"Unknown decoration '$1'": "Decoração '$1' desconhecida",
	"Unknown font $1": "Fonte $1 desconhecida",
	"Unknown glyph: '$1'": "Glifo '$1' desconhecido",
	"Unknown keyword '$1' in %%multicol": "Palavra-chave '$1' desconhecida em %%multicol",
	"Unsecure code": "Código inseguro",
	"Unterminated string": "Cadeia de caracteres não terminada",
	"Unterminated string in Q:": "Cadeia de caracteres não terminada em Q:",
	"Variant ending on a left repeat bar":
				"Variant ending on a left repeat bar",
	"Voice overlay already started": "Divisão de vozes já se iniciou",
				"Variante de repetição em início de ritornello",
	"Voice overlay already started": "Divisão de vozes já foi iniciada",
	"w: without music": "w: sem música",
	"Wrong duration in voice overlay":
				"Duração incorreta na divisão de vozes",
				"Duração incorreta na divisão de vozes"
}
Changes to font/abc2svg.sfd.
︙
15
16
17
18
19
20
21
22

23
24
25
26
27
28
29
15
16
17
18
19
20
21

22
23
24
25
26
27
28
29







-
+







Layer: 0 1 "Back" 1
Layer: 1 0 "Fore" 0
XUID: [1021 296 1430826605 13506888]
OS2Version: 0
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 0
CreationTime: 1433498007
ModificationTime: 1644676399
ModificationTime: 1654926804
PfmFamily: 17
TTFWeight: 400
TTFWidth: 5
LineGap: 92
VLineGap: 92
Panose: 2 0 5 3 0 0 0 0 0 0
OS2TypoAscent: 0
︙
46
47
48
49
50
51
52
53

54
55
56
57
58
59
60
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60







-
+







Encoding: Custom
Compacted: 1
UnicodeInterp: none
NameList: Adobe Glyph List
DisplaySize: -96
AntiAlias: 1
FitToEm: 1
WinInfo: 80 8 2
WinInfo: 0 8 2
BeginPrivate: 0
EndPrivate
TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
BeginChars: 143 143

StartChar: .nodef
Encoding: 0 0 0
︙
339
340
341
342
343
344
345
346
347


348
349
350
351
352
353
354
355
356
357













358
359
360
361



362
363
364
365



366
367

368
369
370
371
372
373
374
375
376
377











378
379
380
381
382
383


384
385
386
387
388
389
390
339
340
341
342
343
344
345


346
347
348
349
350







351
352
353
354
355
356
357
358
359
360
361
362
363




364
365
366




367
368
369


370










371
372
373
374
375
376
377
378
379
380
381






382
383
384
385
386
387
388
389
390







-
-
+
+



-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+







 203 2 167 51 167 91 c 0
EndSplineSet
Validated: 1
EndChar

StartChar: uniE048
Encoding: 10 57416 10
Width: 862
Flags: MW
Width: 954
Flags: HMW
LayerCount: 2
Fore
SplineSet
472 596 m 1
 512 596 l 1
 512 459 l 1
 625 448 715 344 724 216 c 1
 862 216 l 1
 862 176 l 1
 724 176 l 1
452 866 m 1
 504 866 l 1
 504 695 l 1
 653 680 772 545 784 374 c 1
 956 374 l 1
 956 322 l 1
 784 322 l 1
 772 153 653 16 504 1 c 1
 504 -170 l 1
 452 -170 l 1
 452 1 l 1
 303 16 184 153 172 322 c 1
 -0 322 l 1
 715 48 625 -56 512 -67 c 1
 512 -204 l 1
 472 -204 l 1
 472 -67 l 1
 -0 374 l 1
 172 374 l 1
 184 548 303 680 452 695 c 1
 359 -56 269 48 260 176 c 1
 122 176 l 1
 122 216 l 1
 260 216 l 1
 452 866 l 1
452 322 m 1
 304 322 l 1
 269 348 359 448 472 459 c 1
 472 596 l 1
 309 136 373 69 452 58 c 1
472 176 m 1
 360 176 l 1
 364 35 412 -16 472 -24 c 1
 472 176 l 1
512 176 m 1
 512 -24 l 1
 572 -16 620 35 624 176 c 1
 512 176 l 1
512 216 m 1
 624 216 l 1
 452 322 l 1
504 322 m 1
 504 58 l 1
 583 69 647 134 652 322 c 1
 504 322 l 1
504 374 m 1
 652 374 l 1
 647 563 583 627 504 638 c 1
 504 374 l 1
452 374 m 1
 452 640 l 1
 620 358 572 408 512 416 c 1
 512 216 l 1
472 216 m 1
 472 417 l 1
 412 409 364 361 360 216 c 1
 472 216 l 1
 373 629 309 565 304 374 c 1
 452 374 l 1
EndSplineSet
Validated: 1
EndChar

StartChar: uniE050
Encoding: 11 57424 11
Width: 779
︙
765
766
767
768
769
770
771
772
773
774
775
776






777
778
779



780
781
782
783
784
785
786
787
788







789
790
791


792
793
794
795
796
797
798
799
800
801
802
803
804
805



















806
807
808
809






810
811

812
813
814
815
816
817
818
765
766
767
768
769
770
771





772
773
774
775
776
777
778


779
780
781
782
783







784
785
786
787
788
789
790
791


792
793
794













795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813




814
815
816
817
818
819


820
821
822
823
824
825
826
827







-
-
-
-
-
+
+
+
+
+
+

-
-
+
+
+


-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
-
-
+







Encoding: 19 57470 19
Width: 360
Flags: HMW
LayerCount: 2
Fore
SplineSet
212 -1 m 0
 166 -1 150 22 150 54 c 0
 150 73 158 87 174 87 c 0
 189 87 201 79 201 60 c 0
 201 45 183 39 183 25 c 0
 183 13 200 11 212 11 c 0
 171 -1 150 17 150 54 c 0
 150 75 159 87 174 87 c 0
 191 87 201 77 201 60 c 0
 201 53 196 47 192 42 c 0
 188 37 183 32 183 25 c 0
 183 16 193 11 212 11 c 0
 243 11 279 66 279 112 c 0
 279 131 272 140 249 140 c 0
 217 140 201 115 189 115 c 0
 279 132 271 140 249 140 c 0
 238 140 229 134 219 128 c 0
 209 122 200 115 189 115 c 0
 180 115 178 118 178 130 c 0
 178 140 198 196 216 236 c 24
 219 242 224 247 231 247 c 0
 273 248 297 248 339 248 c 0
 347 248 359 250 359 241 c 0
 359 229 349 222 337 218 c 0
 300 205 276 201 239 193 c 0
 224 190 224 186 222 174 c 0
 220 164 220 157 233 161 c 0
 220 243 225 247 231 247 c 0
 266 248 284 248 339 248 c 0
 358 248 359 248 359 241 c 0
 359 231 352 223 337 218 c 0
 309 208 308 208 239 193 c 0
 226 190 224 189 222 174 c 0
 219 161 222 158 233 161 c 0
 241 163 250 164 259 164 c 0
 305 164 326 135 326 96 c 0
 326 39 269 -1 212 -1 c 0
 301 164 326 139 326 96 c 0
 326 43 274 -1 212 -1 c 0
9 0 m 0
 -2.48824948263 1.68260667765e-07 -3.48824945257 18.0000001683 9 18 c 0
 12 17.9999999596 14 18 18 18 c 0
 27 18 39 19 43 30 c 0
 62 75 65 84 84 134 c 0
 89 147 90 155 78 155 c 0
 69 155 66 152 61 146 c 0
 54 138 46 133 36 138 c 0
 25 144 34 153 41 162 c 0
 76 207 112 259 125 259 c 0
 140 259 150 246 165 246 c 0
 168 246 172 247 175 248 c 0
 191 253 196 249 192 238 c 0
 155 148 122 67 111 36 c 0
 -3 0 -3 18 9 18 c 0
 12 18 14 18 18 18 c 0
 32 18 40 22 43 30 c 0
 44 31 44 33 45 34 c 0
 58 65 58 65 81 126 c 0
 82 129 83 131 84 134 c 0
 90 150 88 155 78 155 c 0
 70 155 67 153 61 146 c 0
 52 136 45 134 36 138 c 0
 28 143 28 146 41 162 c 0
 51 174 59 186 66 195 c 0
 111 253 116 259 120 259 c 0
 121 259 121 259 122 259 c 0
 123 259 124 259 125 259 c 0
 133 259 139 255 145 252 c 0
 151 249 157 246 165 246 c 0
 168 246 171 247 175 248 c 0
 190 253 196 249 192 238 c 0
 144 121 120 62 111 36 c 0
 109.509271996 31.8259615875 108.551888787 28.5408311683 108.551888787 25.9855943378 c 0
 108.551888787 19.9703983902 113.857348821 18 130 18 c 4
 137.838821814 18 142.400463498 13.0842298053 142.400463498 8.39053563394 c 0
 142.400463498 4.10259784076 138.593413823 -8.881784197e-16 130 0 c 0
 109 31 109 28 109 26 c 0
 109 20 114 18 130 18 c 0
 137 18 142 13 142 8 c 0
 142 3 138 0 130 0 c 0
 115 0 103 0 91 -0 c 0
 79 -0 67 0 52 -0 c 0
 95.3333333333 0 76.6666666667 -3.45002388166e-07 52.3703703704 -3.45002388166e-07 c 0
 40.2222222222 -3.45002388166e-07 26.6666666667 -2.58751791125e-07 9 0 c 0
 38 0 25 0 9 0 c 0
EndSplineSet
Validated: 1
EndChar

StartChar: uniE080
Encoding: 20 57472 20
Width: 430
︙
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509




2510
2511
2512
2513
2514




2515
2516
2517
2518
2519
2520




2521
2522
2523
2524
2525




2526

2527
2528
2529
2530
2531
2532
2533
2508
2509
2510
2511
2512
2513
2514




2515
2516
2517
2518
2519




2520
2521
2522
2523
2524
2525




2526
2527
2528
2529
2530




2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543







-
-
-
-
+
+
+
+

-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+

-
-
-
-
+
+
+
+

+







Width: 400
Flags: HMW
LayerCount: 2
Fore
SplineSet
152 35 m 2
 357 483 l 2
 363.701063819 494.913002345 369.847754103 501.282269343 377.916665809 501.282269343 c 0
 380.684870018 501.282269343 383.679314405 500.532624121 387 499 c 0
 394.855750978 495.072124511 397.988581796 490.19966499 397.988581796 483.852591655 c 0
 397.988581796 478.888330538 396.072124511 473.021998882 393 466 c 2
 364 495 370 501 378 501 c 0
 381 501 384 501 387 499 c 0
 395 495 398 490 398 484 c 0
 398 479 396 473 393 466 c 2
 189 17 l 2
 183.913450587 6.82690117401 177.770860973 -0.178077048204 169.421173872 -0.178077048204 c 0
 166.280201006 -0.178077048204 162.826901174 0.813199329137 159 3 c 0
 151.569978236 7.33417936247 147.973652542 13.5852067603 147.973652542 20.8036006853 c 0
 147.973652542 25.2434131355 149.334179362 30.0491902446 152 35 c 2
 184 7 178 -0 169 -0 c 0
 166 -0 163 1 159 3 c 0
 152 7 148 14 148 21 c 0
 148 25 149 30 152 35 c 2
9 35 m 2
 214 483 l 2
 220.701063819 494.913002345 226.847754103 501.282269343 234.916665809 501.282269343 c 0
 237.684870018 501.282269343 240.679314405 500.532624121 244 499 c 0
 251.855750978 495.072124511 254.988581796 490.19966499 254.988581796 483.852591655 c 0
 254.988581796 478.888330538 253.072124511 473.021998882 250 466 c 2
 221 495 227 501 235 501 c 0
 238 501 241 501 244 499 c 0
 252 495 255 490 255 484 c 0
 255 479 253 473 250 466 c 2
 46 17 l 2
 40.913450587 6.82690117401 34.7708609728 -0.178077048204 26.4211738717 -0.178077048204 c 0
 23.2802010063 -0.178077048204 19.826901174 0.813199329137 16 3 c 0
 8.56997823576 7.33417936247 4.97365254215 13.5852067603 4.97365254215 20.8036006853 c 0
 4.97365254215 25.2434131355 6.33417936247 30.0491902446 9 35 c 2
 41 7 35 -0 26 -0 c 0
 23 -0 20 1 16 3 c 0
 9 7 5 14 5 21 c 0
 5 25 6 30 9 35 c 2
EndSplineSet
Validated: 1
EndChar

StartChar: uniE4E1
Encoding: 83 58593 83
Width: 130
Flags: HMW
LayerCount: 2
︙
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090








3091
3092
3093
3094
3095
3096
3097
3098
3099








3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110


3111
3112
3113
3114
3115
3116
3117
3086
3087
3088
3089
3090
3091
3092








3093
3094
3095
3096
3097
3098
3099
3100
3101








3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118


3119
3120
3121
3122
3123
3124
3125
3126
3127







-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+









-
-
+
+







Encoding: 101 58665 101
Width: 703
Flags: HMW
LayerCount: 2
Fore
SplineSet
522 40 m 0
 524.179123831 39.0525548563 526.555731167 38.5988184777 529.0907001 38.5988184777 c 0
 553.311569742 38.5988184777 591.989634802 80.0214592618 611 128 c 0
 618.801273837 147.763227053 623.897664408 174.829638922 623.897664408 197.52305288 c 0
 623.897664408 218.46369363 619.558131706 235.680764302 609 240 c 0
 607.059714009 240.759242344 604.934395026 241.11995209 602.659464452 241.11995209 c 0
 577.96754404 241.11995209 535.650235832 198.625589581 521 162 c 0
 510.665333312 135.693575703 506.068143327 108.945807045 506.068143327 87.1471403297 c 0
 506.068143327 62.5417301443 511.925393958 44.2419393862 522 40 c 0
 524 39 526 39 529 39 c 0
 553 39 592 80 611 128 c 0
 619 148 624 175 624 198 c 0
 624 219 620 236 609 240 c 0
 607 241 605 241 603 241 c 0
 578 241 536 199 521 162 c 0
 511 136 506 109 506 87 c 0
 506 62 512 44 522 40 c 0
197 40 m 0
 199.179123831 39.0525548563 201.555731167 38.5988184777 204.0907001 38.5988184777 c 0
 228.311569742 38.5988184777 266.989634802 80.0214592618 286 128 c 0
 293.801273837 147.763227053 298.897664408 174.829638922 298.897664408 197.52305288 c 0
 298.897664408 218.46369363 294.558131706 235.680764302 284 240 c 0
 282.059714009 240.759242344 279.934395026 241.11995209 277.659464452 241.11995209 c 0
 252.96754404 241.11995209 210.650235832 198.625589581 196 162 c 0
 185.665333312 135.693575703 181.068143327 108.945807045 181.068143327 87.1471403297 c 0
 181.068143327 62.5417301443 186.925393958 44.2419393862 197 40 c 0
 199 39 201 39 204 39 c 0
 228 39 267 80 286 128 c 0
 294 148 299 175 299 198 c 0
 299 219 295 236 284 240 c 0
 282 241 280 241 278 241 c 0
 253 241 211 199 196 162 c 0
 186 136 181 109 181 87 c 0
 181 62 187 44 197 40 c 0
200 0 m 0
 183 0 173 6 153 32 c 1
 108 -90 l 1
 160 -90 l 2
 175 -90 175 -120 160 -120 c 2
 -65 -120 l 2
 -79 -120 -80 -90 -65 -90 c 2
 10 -90 l 1
 114 195 l 2
 115.167116963 197.917792406 120.420719938 212.754502351 120.420719938 224.605732511 c 0
 120.420719938 233.06305846 117.745271893 240 109 240 c 0
 115 198 120 213 120 225 c 0
 120 233 118 240 109 240 c 0
 83 240 72 207 28 135 c 0
 21 124 -6 139 1 151 c 0
 56 249 69 280 125 280 c 0
 170 280 193 271 203 235 c 1
 244 278 263 280 305 280 c 0
 331 280 358 264 371 230 c 1
 395 267 413 280 450 280 c 0
︙
3131
3132
3133
3134
3135
3136
3137
3138
3139


3140
3141

3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152


3153
3154

3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165


3166
3167

3168
3169
3170
3171
3172
3173
3174
3175
3176
3177








3178
3179
3180
3181
3182
3183
3184
3185
3186








3187
3188
3189
3190
3191
3192
3193
3194
3195
3196


3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210


3211
3212

3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223


3224
3225

3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236


3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260








3261
3262
3263
3264
3265
3266
3267
3268
3269








3270
3271
3272
3273
3274
3275
3276
3277
3278








3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300


3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321


3322
3323

3324
3325
3326
3327
3328
3329
3330
3331
3332
3333








3334
3335
3336
3337
3338
3339
3340
3341
3342








3343
3344
3345
3346
3347
3348
3349
3141
3142
3143
3144
3145
3146
3147


3148
3149
3150

3151
3152
3153
3154
3155
3156
3157
3158
3159
3160


3161
3162
3163

3164
3165
3166
3167
3168
3169
3170
3171
3172
3173


3174
3175
3176

3177
3178
3179








3180
3181
3182
3183
3184
3185
3186
3187
3188








3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204


3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218


3219
3220
3221

3222
3223
3224
3225
3226
3227
3228
3229
3230
3231


3232
3233
3234

3235
3236
3237
3238
3239
3240
3241
3242
3243
3244


3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262








3263
3264
3265
3266
3267
3268
3269
3270
3271








3272
3273
3274
3275
3276
3277
3278
3279
3280








3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308


3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329


3330
3331
3332

3333
3334
3335








3336
3337
3338
3339
3340
3341
3342
3343
3344








3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359







-
-
+
+

-
+









-
-
+
+

-
+









-
-
+
+

-
+


-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+








-
-
+
+












-
-
+
+

-
+









-
-
+
+

-
+









-
-
+
+
















-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+




















-
-
+
+



















-
-
+
+

-
+


-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+







 1083 -90 l 1
 1135 -90 l 2
 1150 -90 1150 -120 1135 -120 c 2
 910 -120 l 2
 896 -120 895 -90 910 -90 c 2
 985 -90 l 1
 1089 195 l 2
 1090.16711696 197.917792406 1095.42071994 212.754502351 1095.42071994 224.605732511 c 0
 1095.42071994 233.06305846 1092.74527189 240 1084 240 c 0
 1090 198 1095 213 1095 225 c 0
 1095 233 1093 240 1084 240 c 0
 1064 240 1053 221 1030 181 c 0
 1030 181 1030 180 1030 180 c 0
 1030 180 l 0
 1030 87 949 0 850 0 c 0
 833 0 823 6 803 32 c 1
 758 -90 l 1
 810 -90 l 2
 825 -90 825 -120 810 -120 c 2
 585 -120 l 2
 571 -120 570 -90 585 -90 c 2
 660 -90 l 1
 764 195 l 2
 765.167116963 197.917792406 770.420719938 212.754502351 770.420719938 224.605732511 c 0
 770.420719938 233.06305846 767.745271893 240 759 240 c 0
 765 198 770 213 770 225 c 0
 770 233 768 240 759 240 c 0
 739 240 728 221 705 181 c 0
 705 181 705 180 705 180 c 0
 705 180 l 0
 705 87 624 0 525 0 c 0
 508 0 498 6 478 32 c 1
 433 -90 l 1
 485 -90 l 2
 500 -90 500 -120 485 -120 c 2
 260 -120 l 2
 246 -120 245 -90 260 -90 c 2
 335 -90 l 1
 439 195 l 2
 440.167116963 197.917792406 445.420719938 212.754502351 445.420719938 224.605732511 c 0
 445.420719938 233.06305846 442.745271893 240 434 240 c 0
 440 198 445 213 445 225 c 0
 445 233 443 240 434 240 c 0
 414 240 403 221 380 181 c 0
 380 181 380 180 380 180 c 0
 380 180 l 0
 380 87 299 0 200 0 c 0
1172 40 m 0
 1174.17912383 39.0525548563 1176.55573117 38.5988184777 1179.0907001 38.5988184777 c 0
 1203.31156974 38.5988184777 1241.9896348 80.0214592618 1261 128 c 0
 1268.80127384 147.763227053 1273.89766441 174.829638922 1273.89766441 197.52305288 c 0
 1273.89766441 218.46369363 1269.55813171 235.680764302 1259 240 c 0
 1257.05971401 240.759242344 1254.93439503 241.11995209 1252.65946445 241.11995209 c 0
 1227.96754404 241.11995209 1185.65023583 198.625589581 1171 162 c 0
 1160.66533331 135.693575703 1156.06814333 108.945807045 1156.06814333 87.1471403297 c 0
 1156.06814333 62.5417301443 1161.92539396 44.2419393862 1172 40 c 0
 1174 39 1176 39 1179 39 c 0
 1203 39 1242 80 1261 128 c 0
 1269 148 1274 175 1274 198 c 0
 1274 219 1270 236 1259 240 c 0
 1257 241 1255 241 1253 241 c 0
 1228 241 1186 199 1171 162 c 0
 1161 136 1156 109 1156 87 c 0
 1156 62 1162 44 1172 40 c 0
847 40 m 0
 849.179123831 39.0525548563 851.555731167 38.5988184777 854.0907001 38.5988184777 c 0
 878.311569742 38.5988184777 916.989634802 80.0214592618 936 128 c 0
 943.801273837 147.763227053 948.897664408 174.829638922 948.897664408 197.52305288 c 0
 948.897664408 218.46369363 944.558131706 235.680764302 934 240 c 0
 932.059714009 240.759242344 929.934395026 241.11995209 927.659464452 241.11995209 c 0
 902.96754404 241.11995209 860.650235832 198.625589581 846 162 c 0
 835.665333312 135.693575703 831.068143327 108.945807045 831.068143327 87.1471403297 c 0
 831.068143327 62.5417301443 836.925393958 44.2419393862 847 40 c 0
 849 39 851 39 854 39 c 0
 878 39 917 80 936 128 c 0
 944 148 949 175 949 198 c 0
 949 219 945 236 934 240 c 0
 932 241 930 241 928 241 c 0
 903 241 861 199 846 162 c 0
 836 136 831 109 831 87 c 0
 831 62 837 44 847 40 c 0
EndSplineSet
Validated: 1
EndChar

StartChar: uniE52A
Encoding: 102 58666 102
Width: 563
Flags: W
HStem: -120 30<-74.2249 10 108 169.668 250.775 335 433 494.668 575.775 660 758 819.668> 0 38.5988<191.014 237.319 516.014 562.319 841.014 887.319> 240 40<80.1149 147.5 239.117 318 404.386 472.5 564.117 643 729.386 797.5 889.117 973>
VStem: 120.421 60.6474<87.1471 224.606> 298.898 81.1023<108.679 217.246> 445.421 60.6474<87.1471 224.606> 623.898 81.1023<108.679 217.246> 770.421 60.6474<87.1471 224.606> 948.898 81.1023<108.462 239.669>
HStem: -120 30<-74.2249 10 108 169.668 250.775 335 433 494.668 575.775 660 758 819.668> 0 39<191.01 237.957 516.01 562.957 841.01 887.957> 240 40<80.1149 147.5 239.21 318 404.386 472.5 564.21 643 729.386 797.5 889.21 973>
VStem: 120 61<87 225> 299 81<108.679 217.246> 445 61<87 225> 624 81<108.679 217.246> 770 61<87 225> 949 81<108.469 239.676>
LayerCount: 2
Fore
SplineSet
850 0 m 0
 833 0 823 6 803 32 c 1
 758 -90 l 1
 810 -90 l 2
 825 -90 825 -120 810 -120 c 2
 585 -120 l 2
 571 -120 570 -90 585 -90 c 2
 660 -90 l 1
 764 195 l 2
 765.167116963 197.917792406 770.420719938 212.754502351 770.420719938 224.605732511 c 0
 770.420719938 233.06305846 767.745271893 240 759 240 c 0
 765 198 770 213 770 225 c 0
 770 233 768 240 759 240 c 0
 739 240 728 221 705 181 c 0
 705 181 705 180 705 180 c 0
 705 180 l 0
 705 87 624 0 525 0 c 0
 508 0 498 6 478 32 c 1
 433 -90 l 1
 485 -90 l 2
 500 -90 500 -120 485 -120 c 2
 260 -120 l 2
 246 -120 245 -90 260 -90 c 2
 335 -90 l 1
 439 195 l 2
 440.167116963 197.917792406 445.420719938 212.754502351 445.420719938 224.605732511 c 0
 445.420719938 233.06305846 442.745271893 240 434 240 c 0
 440 198 445 213 445 225 c 0
 445 233 443 240 434 240 c 0
 414 240 403 221 380 181 c 0
 380 181 380 180 380 180 c 0
 380 180 l 0
 380 87 299 0 200 0 c 0
 183 0 173 6 153 32 c 1
 108 -90 l 1
 160 -90 l 2
 175 -90 175 -120 160 -120 c 2
 -65 -120 l 2
 -79 -120 -80 -90 -65 -90 c 2
 10 -90 l 1
 114 195 l 2
 115.167116963 197.917792406 120.420719938 212.754502351 120.420719938 224.605732511 c 0
 120.420719938 233.06305846 117.745271893 240 109 240 c 0
 115 198 120 213 120 225 c 0
 120 233 118 240 109 240 c 0
 83 240 72 207 28 135 c 0
 21 124 -6 139 1 151 c 0
 56 249 69 280 125 280 c 0
 170 280 193 271 203 235 c 1
 244 278 263 280 305 280 c 0
 331 280 358 264 371 230 c 1
 395 267 413 280 450 280 c 0
 495 280 518 271 528 235 c 1
 569 278 588 280 630 280 c 0
 656 280 683 264 696 230 c 1
 720 267 738 280 775 280 c 0
 820 280 843 271 853 235 c 1
 894 278 913 280 955 280 c 0
 991 280 1030 248 1030 180 c 0
 1030 87 949 0 850 0 c 0
847 40 m 0
 849.179123831 39.0525548563 851.555731167 38.5988184777 854.0907001 38.5988184777 c 0
 878.311569742 38.5988184777 916.989634802 80.0214592618 936 128 c 0
 943.801273837 147.763227053 948.897664408 174.829638922 948.897664408 197.52305288 c 0
 948.897664408 218.46369363 944.558131706 235.680764302 934 240 c 0
 932.059714009 240.759242344 929.934395026 241.11995209 927.659464452 241.11995209 c 0
 902.96754404 241.11995209 860.650235832 198.625589581 846 162 c 0
 835.665333312 135.693575703 831.068143327 108.945807045 831.068143327 87.1471403297 c 0
 831.068143327 62.5417301443 836.925393958 44.2419393862 847 40 c 0
 849 39 851 39 854 39 c 0
 878 39 917 80 936 128 c 0
 944 148 949 175 949 198 c 0
 949 219 945 236 934 240 c 0
 932 241 930 241 928 241 c 0
 903 241 861 199 846 162 c 0
 836 136 831 109 831 87 c 0
 831 62 837 44 847 40 c 0
522 40 m 0
 524.179123831 39.0525548563 526.555731167 38.5988184777 529.0907001 38.5988184777 c 0
 553.311569742 38.5988184777 591.989634802 80.0214592618 611 128 c 0
 618.801273837 147.763227053 623.897664408 174.829638922 623.897664408 197.52305288 c 0
 623.897664408 218.46369363 619.558131706 235.680764302 609 240 c 0
 607.059714009 240.759242344 604.934395026 241.11995209 602.659464452 241.11995209 c 0
 577.96754404 241.11995209 535.650235832 198.625589581 521 162 c 0
 510.665333312 135.693575703 506.068143327 108.945807045 506.068143327 87.1471403297 c 0
 506.068143327 62.5417301443 511.925393958 44.2419393862 522 40 c 0
 524 39 526 39 529 39 c 0
 553 39 592 80 611 128 c 0
 619 148 624 175 624 198 c 0
 624 219 620 236 609 240 c 0
 607 241 605 241 603 241 c 0
 578 241 536 199 521 162 c 0
 511 136 506 109 506 87 c 0
 506 62 512 44 522 40 c 0
197 40 m 0
 199.179123831 39.0525548563 201.555731167 38.5988184777 204.0907001 38.5988184777 c 0
 228.311569742 38.5988184777 266.989634802 80.0214592618 286 128 c 0
 293.801273837 147.763227053 298.897664408 174.829638922 298.897664408 197.52305288 c 0
 298.897664408 218.46369363 294.558131706 235.680764302 284 240 c 0
 282.059714009 240.759242344 279.934395026 241.11995209 277.659464452 241.11995209 c 0
 252.96754404 241.11995209 210.650235832 198.625589581 196 162 c 0
 185.665333312 135.693575703 181.068143327 108.945807045 181.068143327 87.1471403297 c 0
 181.068143327 62.5417301443 186.925393958 44.2419393862 197 40 c 0
 199 39 201 39 204 39 c 0
 228 39 267 80 286 128 c 0
 294 148 299 175 299 198 c 0
 299 219 295 236 284 240 c 0
 282 241 280 241 278 241 c 0
 253 241 211 199 196 162 c 0
 186 136 181 109 181 87 c 0
 181 62 187 44 197 40 c 0
EndSplineSet
Validated: 1
EndChar

StartChar: uniE52B
Encoding: 103 58667 103
Width: 703
Flags: HMW
LayerCount: 2
Fore
SplineSet
200 0 m 0
 183 0 173 6 153 32 c 1
 108 -90 l 1
 160 -90 l 2
 175 -90 175 -120 160 -120 c 2
 -65 -120 l 2
 -79 -120 -80 -90 -65 -90 c 2
 10 -90 l 1
 114 195 l 2
 115.167116963 197.917792406 120.420719938 212.754502351 120.420719938 224.605732511 c 0
 120.420719938 233.06305846 117.745271893 240 109 240 c 0
 115 198 120 213 120 225 c 0
 120 233 118 240 109 240 c 0
 83 240 72 207 28 135 c 0
 21 124 -6 139 1 151 c 0
 56 249 69 280 125 280 c 0
 170 280 193 271 203 235 c 1
 244 278 263 280 305 280 c 0
 331 280 358 264 371 230 c 1
 395 267 413 280 450 280 c 0
 495 280 518 271 528 235 c 1
 569 278 588 280 630 280 c 0
 666 280 705 248 705 180 c 0
 705 87 624 0 525 0 c 0
 508 0 498 6 478 32 c 1
 433 -90 l 1
 485 -90 l 2
 500 -90 500 -120 485 -120 c 2
 260 -120 l 2
 246 -120 245 -90 260 -90 c 2
 335 -90 l 1
 439 195 l 2
 440.167116963 197.917792406 445.420719938 212.754502351 445.420719938 224.605732511 c 0
 445.420719938 233.06305846 442.745271893 240 434 240 c 0
 440 198 445 213 445 225 c 0
 445 233 443 240 434 240 c 0
 414 240 403 221 380 181 c 0
 380 181 380 180 380 180 c 0
 380 180 l 0
 380 87 299 0 200 0 c 0
197 40 m 0
 199.179123831 39.0525548563 201.555731167 38.5988184777 204.0907001 38.5988184777 c 0
 228.311569742 38.5988184777 266.989634802 80.0214592618 286 128 c 0
 293.801273837 147.763227053 298.897664408 174.829638922 298.897664408 197.52305288 c 0
 298.897664408 218.46369363 294.558131706 235.680764302 284 240 c 0
 282.059714009 240.759242344 279.934395026 241.11995209 277.659464452 241.11995209 c 0
 252.96754404 241.11995209 210.650235832 198.625589581 196 162 c 0
 185.665333312 135.693575703 181.068143327 108.945807045 181.068143327 87.1471403297 c 0
 181.068143327 62.5417301443 186.925393958 44.2419393862 197 40 c 0
 199 39 201 39 204 39 c 0
 228 39 267 80 286 128 c 0
 294 148 299 175 299 198 c 0
 299 219 295 236 284 240 c 0
 282 241 280 241 278 241 c 0
 253 241 211 199 196 162 c 0
 186 136 181 109 181 87 c 0
 181 62 187 44 197 40 c 0
522 40 m 0
 524.179123831 39.0525548563 526.555731167 38.5988184777 529.0907001 38.5988184777 c 0
 553.311569742 38.5988184777 591.989634802 80.0214592618 611 128 c 0
 618.801273837 147.763227053 623.897664408 174.829638922 623.897664408 197.52305288 c 0
 623.897664408 218.46369363 619.558131706 235.680764302 609 240 c 0
 607.059714009 240.759242344 604.934395026 241.11995209 602.659464452 241.11995209 c 0
 577.96754404 241.11995209 535.650235832 198.625589581 521 162 c 0
 510.665333312 135.693575703 506.068143327 108.945807045 506.068143327 87.1471403297 c 0
 506.068143327 62.5417301443 511.925393958 44.2419393862 522 40 c 0
 524 39 526 39 529 39 c 0
 553 39 592 80 611 128 c 0
 619 148 624 175 624 198 c 0
 624 219 620 236 609 240 c 0
 607 241 605 241 603 241 c 0
 578 241 536 199 521 162 c 0
 511 136 506 109 506 87 c 0
 506 62 512 44 522 40 c 0
EndSplineSet
Validated: 1
EndChar

StartChar: uniE52C
Encoding: 104 58668 104
Width: 811
︙
3361
3362
3363
3364
3365
3366
3367
3368
3369


3370
3371
3372
3373


3374
3375
3376
3377



3378
3379
3380
3381
3382
3383
3384
3385



3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400


3401
3402
3403
3404



3405
3406
3407
3408
3409
3410
3411
3412
3413
3414








3415
3416
3417
3418
3419
3420
3421
3371
3372
3373
3374
3375
3376
3377


3378
3379
3380
3381


3382
3383
3384



3385
3386
3387
3388
3389
3390
3391
3392



3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408


3409
3410
3411



3412
3413
3414
3415
3416








3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431







-
-
+
+


-
-
+
+

-
-
-
+
+
+





-
-
-
+
+
+













-
-
+
+

-
-
-
+
+
+


-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+







 539 -90 l 1
 591 -90 l 2
 606 -90 606 -120 591 -120 c 2
 366 -120 l 2
 352 -120 351 -90 366 -90 c 2
 441 -90 l 1
 545 195 l 2
 546.167116963 197.917792406 551.420719938 212.754502351 551.420719938 224.605732511 c 0
 551.420719938 233.06305846 548.745271893 240 540 240 c 0
 546 198 551 213 551 225 c 0
 551 233 549 240 540 240 c 0
 514 240 491 175 454 100 c 0
 423 37 388 -10 323 -10 c 0
 295.96461562 -10 276.685848474 4.31990873934 276.685848474 30.6552962356 c 0
 276.685848474 38.4136929968 278.359032859 47.2149008096 282 57 c 2
 296 -10 277 5 277 31 c 0
 277 39 278 47 282 57 c 2
 334 193 l 2
 334.687448544 195.062345631 335.007330587 196.961328373 335.007330587 198.698285174 c 0
 335.007330587 208.224115412 325.386390673 212.876768553 314.009865299 212.876768553 c 0
 300.302784706 212.876768553 284.047157224 206.122608782 279 193 c 2
 335 195 335 197 335 199 c 0
 335 209 325 213 314 213 c 0
 300 213 284 206 279 193 c 2
 214 22 l 2
 211 14 207 0 191 -1 c 2
 153 0 l 2
 134 -2 132 12 136 22 c 2
 203 193 l 2
 203.698722351 195.018531237 204.023750859 196.886379473 204.023750859 198.6021409 c 0
 204.023750859 208.040661137 194.187819777 212.876768553 182.617370253 212.876768553 c 0
 169.199127766 212.876768553 153.448127054 206.372675496 148 193 c 2
 204 195 204 197 204 199 c 0
 204 208 195 213 183 213 c 0
 170 213 153 206 148 193 c 2
 80 21 l 2
 76 11 74 1 57 0 c 2
 4 0 l 2
 -6 0 0 18 4 26 c 2
 70 193 l 2
 77 211 69 221 60 221 c 0
 42 221 35 210 -11 138 c 0
 -18 127 -39 139 -33 151 c 0
 3 216 36 280 86 280 c 0
 128 280 145 266 152 240 c 1
 170 260 194 280 224 280 c 0
 254 280 273 263 278 240 c 1
 292 259 322 280 349 280 c 0
 382.297060451 280 409.658220776 253.618221661 409.658220776 218.530504648 c 0
 409.658220776 210.41331474 408.193901764 201.830198995 405 193 c 2
 382 280 410 254 410 219 c 0
 410 211 408 202 405 193 c 2
 358 68 l 2
 355.211869734 60.3326417674 353.881302664 53.2726015335 353.881302664 47.6241881024 c 0
 353.881302664 39.5840041338 356.577277218 34.4040367054 361.602949871 34.4040367054 c 0
 363.177144158 34.4040367054 364.979913018 34.9122608557 367 36 c 0
 355 60 354 54 354 48 c 0
 354 40 357 34 362 34 c 0
 364 34 365 35 367 36 c 0
 389 48 418 94 429 119 c 0
628 40 m 0
 630.179123831 39.0525548563 632.555731167 38.5988184777 635.0907001 38.5988184777 c 0
 659.311569742 38.5988184777 697.989634802 80.0214592618 717 128 c 0
 724.801273837 147.763227053 729.897664408 174.829638922 729.897664408 197.52305288 c 0
 729.897664408 218.46369363 725.558131706 235.680764302 715 240 c 0
 713.059714009 240.759242344 710.934395026 241.11995209 708.659464452 241.11995209 c 0
 683.96754404 241.11995209 641.650235832 198.625589581 627 162 c 0
 616.665333312 135.693575703 612.068143327 108.945807045 612.068143327 87.1471403297 c 0
 612.068143327 62.5417301443 617.925393958 44.2419393862 628 40 c 0
 630 39 632 39 635 39 c 0
 659 39 698 80 717 128 c 0
 725 148 730 175 730 198 c 0
 730 219 726 236 715 240 c 0
 713 241 711 241 709 241 c 0
 684 241 642 199 627 162 c 0
 617 136 612 109 612 87 c 0
 612 62 618 44 628 40 c 0
EndSplineSet
Validated: 1
EndChar

StartChar: uniE52D
Encoding: 105 58669 105
Width: 351
︙
3432
3433
3434
3435
3436
3437
3438
3439
3440


3441
3442
3443
3444



3445
3446
3447
3448
3449


3450
3451
3452
3453



3454
3455
3456
3457
3458
3459
3460
3461



3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473


3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485


3486
3487
3488
3489
3490
3491
3492
3442
3443
3444
3445
3446
3447
3448


3449
3450
3451



3452
3453
3454
3455
3456
3457


3458
3459
3460



3461
3462
3463
3464
3465
3466
3467
3468



3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481


3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493


3494
3495
3496
3497
3498
3499
3500
3501
3502







-
-
+
+

-
-
-
+
+
+



-
-
+
+

-
-
-
+
+
+





-
-
-
+
+
+










-
-
+
+










-
-
+
+







 42 221 35 210 -11 138 c 0
 -18 127 -39 139 -33 151 c 0
 3 216 36 280 86 280 c 0
 128 280 145 266 152 240 c 1
 170 260 194 280 224 280 c 0
 254 280 273 263 278 240 c 1
 292 259 322 280 349 280 c 0
 382.297060451 280 409.658220776 253.618221661 409.658220776 218.530504648 c 0
 409.658220776 210.41331474 408.193901764 201.830198995 405 193 c 0
 382 280 410 254 410 219 c 0
 410 211 408 202 405 193 c 0
 358 68 l 0
 355.211869734 60.3326417674 353.881302664 53.2726015335 353.881302664 47.6241881024 c 0
 353.881302664 39.5840041338 356.577277218 34.4040367054 361.602949871 34.4040367054 c 0
 363.177144158 34.4040367054 364.979913018 34.9122608557 367 36 c 0
 355 60 354 54 354 48 c 0
 354 40 357 34 362 34 c 0
 364 34 365 35 367 36 c 0
 389 48 404 72 419 93 c 0
 427 104 449 93 440 78 c 0
 411 27 388 -10 323 -10 c 0
 295.96461562 -10 276.685848474 4.31990873934 276.685848474 30.6552962356 c 0
 276.685848474 38.4136929968 278.359032859 47.2149008096 282 57 c 0
 296 -10 277 5 277 31 c 0
 277 39 278 47 282 57 c 0
 334 193 l 0
 334.687448544 195.062345631 335.007330587 196.961328373 335.007330587 198.698285174 c 0
 335.007330587 208.224115412 325.386390673 212.876768553 314.009865299 212.876768553 c 0
 300.302784706 212.876768553 284.047157224 206.122608782 279 193 c 0
 335 195 335 197 335 199 c 0
 335 209 325 213 314 213 c 0
 300 213 284 206 279 193 c 0
 214 22 l 0
 211 14 207 0 191 -1 c 1
 153 0 l 16
 134 -2 132 12 136 22 c 0
 203 193 l 0
 203.698722351 195.018531237 204.023750859 196.886379473 204.023750859 198.6021409 c 0
 204.023750859 208.040661137 194.187819777 212.876768553 182.617370253 212.876768553 c 0
 169.199127766 212.876768553 153.448127054 206.372675496 148 193 c 0
 204 195 204 197 204 199 c 0
 204 208 195 213 183 213 c 0
 170 213 153 206 148 193 c 0
 80 21 l 0
393 -160 m 0
 353 -160 313 -135 313 -90 c 0
 313 -55 330 -30 355 -30 c 0
 380 -30 396 -45 396 -65 c 0
 396 -75 389 -83 378 -90 c 0
 368 -97 368 -100 368 -110 c 0
 368 -120 378 -124 393 -124 c 0
 456 -124 459 -35 526 230 c 1
 507 230 508 230 473 230 c 0
 460.239470207 230 453.992185238 240.421191718 453.992185238 250.625180963 c 0
 453.992185238 260.412503619 459.73968741 270 471 270 c 0
 460 230 454 241 454 251 c 0
 454 261 460 270 471 270 c 0
 502 270 504 270 536 270 c 1
 564 374 647 440 713 440 c 0
 753 440 793 415 793 370 c 0
 793 335 776 310 751 310 c 0
 726 310 710 325 710 345 c 0
 710 355 717 363 728 370 c 0
 739 377 738 380 738 390 c 0
 738 400 728 404 713 404 c 0
 671 404 661 332 648 270 c 1
 675 270 672 270 702 270 c 0
 715 270 721.5 260 721.5 250 c 0
 721.5 240 715 230 702 230 c 0
 715 270 722 260 722 250 c 0
 722 240 715 230 702 230 c 0
 676 230 672 230 639 230 c 1
 596 -26 523 -160 393 -160 c 0
EndSplineSet
Validated: 1
EndChar

StartChar: uniE52F
︙
3503
3504
3505
3506
3507
3508
3509
3510
3511


3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532


3533
3534
3535
3536
3537
3538
3539
3513
3514
3515
3516
3517
3518
3519


3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540


3541
3542
3543
3544
3545
3546
3547
3548
3549







-
-
+
+



















-
-
+
+







 -130 -55 -113 -30 -88 -30 c 0
 -63 -30 -47 -45 -47 -65 c 0
 -47 -75 -54 -83 -65 -90 c 0
 -75 -97 -75 -100 -75 -110 c 0
 -75 -120 -65 -124 -50 -124 c 0
 13 -124 16 -35 83 230 c 1
 64 230 65 230 30 230 c 0
 17.239470207 230 10.992185238 240.421191718 10.992185238 250.625180963 c 0
 10.992185238 260.412503619 16.7396874095 270 28 270 c 0
 17 230 11 241 11 251 c 0
 11 261 17 270 28 270 c 0
 59 270 61 270 93 270 c 1
 121 374 204 440 270 440 c 0
 310 440 350 415 350 370 c 0
 350 335 333 310 308 310 c 0
 283 310 267 325 267 345 c 0
 267 355 274 363 285 370 c 0
 296 377 295 380 295 390 c 0
 295 400 285 404 270 404 c 0
 228 404 218 333 205 271 c 1
 360 270 l 1
 388 374 471 440 537 440 c 0
 577 440 617 415 617 370 c 0
 617 335 600 310 575 310 c 0
 550 310 534 325 534 345 c 0
 534 355 541 363 552 370 c 0
 563 377 562 380 562 390 c 0
 562 400 552 404 537 404 c 0
 495 404 485 332 472 270 c 1
 499 270 496 270 526 270 c 0
 539 270 545.5 260 545.5 250 c 0
 545.5 240 539 230 526 230 c 0
 539 270 546 260 546 250 c 0
 546 240 539 230 526 230 c 0
 500 230 496 230 463 230 c 1
 420 -26 347 -160 217 -160 c 0
 177 -160 137 -135 137 -90 c 0
 137 -55 154 -30 179 -30 c 0
 204 -30 220 -45 220 -65 c 0
 220 -75 213 -83 202 -90 c 0
 192 -97 192 -100 192 -110 c 0
︙
3566
3567
3568
3569
3570
3571
3572
3573
3574


3575
3576
3577
3578
3579
3580
3581
3576
3577
3578
3579
3580
3581
3582


3583
3584
3585
3586
3587
3588
3589
3590
3591







-
-
+
+







 884 335 867 310 842 310 c 0
 817 310 801 325 801 345 c 0
 801 355 808 363 819 370 c 0
 830 377 829 380 829 390 c 0
 829 400 819 404 804 404 c 0
 762 404 752 332 739 270 c 1
 766 270 763 270 793 270 c 0
 806 270 812.5 260 812.5 250 c 0
 812.5 240 806 230 793 230 c 0
 806 270 812 260 812 250 c 0
 812 240 806 230 793 230 c 0
 767 230 763 230 730 230 c 1
 687 -26 614 -160 484 -160 c 0
 444 -160 404 -135 404 -90 c 0
 404 -55 421 -30 446 -30 c 0
 471 -30 487 -45 487 -65 c 0
 487 -75 480 -83 469 -90 c 0
 459 -97 459 -100 459 -110 c 0
︙
3596
3597
3598
3599
3600
3601
3602
3603
3604


3605
3606
3607
3608
3609
3610
3611
3606
3607
3608
3609
3610
3611
3612


3613
3614
3615
3616
3617
3618
3619
3620
3621







-
-
+
+







 -130 -55 -113 -30 -88 -30 c 0
 -63 -30 -47 -45 -47 -65 c 0
 -47 -75 -54 -83 -65 -90 c 0
 -75 -97 -75 -100 -75 -110 c 0
 -75 -120 -65 -124 -50 -124 c 0
 13 -124 16 -35 83 230 c 1
 64 230 65 230 30 230 c 0
 17.239470207 230 10.992185238 240.421191718 10.992185238 250.625180963 c 0
 10.992185238 260.412503619 16.7396874095 270 28 270 c 0
 17 230 11 241 11 251 c 0
 11 261 17 270 28 270 c 0
 59 270 61 270 93 270 c 1
 121 374 204 440 270 440 c 0
 310 440 350 415 350 370 c 0
 350 335 333 310 308 310 c 0
 283 310 267 325 267 345 c 0
 267 355 274 363 285 370 c 0
 296 377 295 380 295 390 c 0
︙
3638
3639
3640
3641
3642
3643
3644
3645
3646


3647
3648
3649
3650
3651
3652
3653
3648
3649
3650
3651
3652
3653
3654


3655
3656
3657
3658
3659
3660
3661
3662
3663







-
-
+
+







 1152 335 1135 310 1110 310 c 0
 1085 310 1069 325 1069 345 c 0
 1069 355 1076 363 1087 370 c 0
 1098 377 1097 380 1097 390 c 0
 1097 400 1087 404 1072 404 c 0
 1030 404 1020 332 1007 270 c 1
 1034 270 1031 270 1061 270 c 0
 1074 270 1080.5 260 1080.5 250 c 0
 1080.5 240 1074 230 1061 230 c 0
 1074 270 1080 260 1080 250 c 0
 1080 240 1074 230 1061 230 c 0
 1035 230 1031 230 998 230 c 1
 955 -26 882 -160 752 -160 c 0
 712 -160 672 -135 672 -90 c 0
 672 -55 689 -30 714 -30 c 0
 739 -30 755 -45 755 -65 c 0
 755 -75 748 -83 737 -90 c 0
 727 -97 727 -100 727 -110 c 0
︙
3677
3678
3679
3680
3681
3682
3683
3684
3685


3686
3687
3688
3689
3690
3691
3692
3687
3688
3689
3690
3691
3692
3693


3694
3695
3696
3697
3698
3699
3700
3701
3702







-
-
+
+







 -130 -55 -113 -30 -88 -30 c 0
 -63 -30 -47 -45 -47 -65 c 0
 -47 -75 -54 -83 -65 -90 c 0
 -75 -97 -75 -100 -75 -110 c 0
 -75 -120 -65 -124 -50 -124 c 0
 13 -124 16 -35 83 230 c 1
 64 230 65 230 30 230 c 0
 17.239470207 230 10.992185238 240.421191718 10.992185238 250.625180963 c 0
 10.992185238 260.412503619 16.7396874095 270 28 270 c 0
 17 230 11 241 11 251 c 0
 11 261 17 270 28 270 c 0
 59 270 61 270 93 270 c 1
 121 374 204 440 270 440 c 0
 310 440 350 415 350 370 c 0
 350 335 333 310 308 310 c 0
 283 310 267 325 267 345 c 0
 267 355 274 363 285 370 c 0
 296 377 295 380 295 390 c 0
︙
3720
3721
3722
3723
3724
3725
3726
3727
3728


3729
3730
3731
3732
3733
3734
3735
3736
3737
3738


3739
3740
3741
3742
3743
3744



3745
3746
3747
3748


3749
3750
3751
3752
3753
3754
3755
3756




3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767


3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779


3780
3781
3782
3783
3784
3785
3786
3730
3731
3732
3733
3734
3735
3736


3737
3738
3739
3740
3741
3742
3743
3744
3745
3746


3747
3748
3749
3750
3751



3752
3753
3754
3755
3756


3757
3758
3759
3760
3761
3762




3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775


3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787


3788
3789
3790
3791
3792
3793
3794
3795
3796







-
-
+
+








-
-
+
+



-
-
-
+
+
+


-
-
+
+




-
-
-
-
+
+
+
+









-
-
+
+










-
-
+
+







 60 34 69 30 85 30 c 0
 113 30 140 45 140 70 c 0
 140 100 120 107 94 124 c 0
 61 145 30 167 30 200 c 0
 30 247 70 280 130 280 c 0
 175 280 218 253 218 220 c 0
 218 199 202 180 180 180 c 0
 163.575188443 180 145.315176651 194.681601887 145.315176651 213.518742217 c 0
 145.315176651 218.765893239 146.732008255 224.335480975 150 230 c 1
 164 180 145 195 145 214 c 0
 145 219 147 224 150 230 c 1
 142 240 133 250 120 250 c 0
 101 250 80 239 80 220 c 0
 80 193 127 175 150 160 c 0
 184 137 200 120 200 90 c 0
 200 26 156 0 80 0 c 0xd8
494 3 m 0
 482 -6 470 16 481 26 c 0
 655 217 l 0
 647.065121876 221.231935 637.451249329 222.385713558 625.122178884 222.385713558 c 0
 614.144520003 222.385713558 601.014276252 221.471008125 585 221 c 0
 647 221 637 222 625 222 c 0
 614 222 601 221 585 221 c 0
 574 221 554 173 541 162 c 0
 527 150 511 153 524 187 c 0
 543 238 542 251 548 267 c 1
 580.854598393 264.712654542 605.061346797 263.852204333 623.335584921 263.852204333 c 0
 678.854023456 263.852204333 679.616120157 271.794054171 701.762784585 271.794054171 c 0
 707.383128795 271.794054171 714.380713088 271.282571588 724 270 c 0
 581 265 605 264 623 264 c 0
 679 264 680 272 702 272 c 0
 708 272 714 271 724 270 c 0
 739 268 737 254 728 245 c 0
 574 82 l 0
 571.290722326 77.5296918374 570.067832218 74.1420610385 570.067832218 71.5487518924 c 0
 570.067832218 54.9981455171 619.876699814 70.8006196048 651 44 c 0
 571 78 570 75 570 72 c 0
 570 55 620 71 651 44 c 0
 660 36 672 52 668 64 c 0
 661 84 629 65 629 100 c 0
 629 115 645 130 661 130 c 0
 681 130 699 117 699 75 c 0
 699 35.6904170379 666.82658183 2.81551770962 623.631918047 2.81551770962 c 0
 621.774432159 2.81551770962 619.896565457 2.87631094845 618 3 c 0
 574.626134889 6.18925478761 553.222377354 17.5155864555 532.50873405 17.5155864555 c 0
 520.748265557 17.5155864555 509.210259784 13.8644712743 494 3 c 0
 699 36 667 3 624 3 c 0
 622 3 620 3 618 3 c 0
 575 6 554 18 533 18 c 0
 521 18 509 14 494 3 c 0
182 -160 m 0
 142 -160 102 -135 102 -90 c 0
 102 -55 119 -30 144 -30 c 0
 169 -30 185 -45 185 -65 c 0
 185 -75 178 -83 167 -90 c 0
 157 -97 165 -100 165 -110 c 0
 165 -120 167 -124 182 -124 c 0
 245 -124 248 -35 315 230 c 1
 296 230 297 230 262 230 c 0
 249.239470207 230 242.992185238 240.421191718 242.992185238 250.625180963 c 0
 242.992185238 260.412503619 248.73968741 270 260 270 c 0
 249 230 243 241 243 251 c 0
 243 261 249 270 260 270 c 0
 291 270 293 270 325 270 c 1
 353 374 436 440 502 440 c 0
 542 440 582 415 582 370 c 0
 582 335 565 310 540 310 c 0
 515 310 499 325 499 345 c 0
 499 355 506 363 517 370 c 0
 528 377 527 380 527 390 c 0
 527 400 517 404 502 404 c 0
 460 404 450 332 437 270 c 1
 464 270 461 270 491 270 c 0
 504 270 510.5 260 510.5 250 c 0
 510.5 240 504 230 491 230 c 0
 504 270 510 260 510 250 c 0
 510 240 504 230 491 230 c 0
 465 230 461 230 428 230 c 1
 385 -26 312 -160 182 -160 c 0
EndSplineSet
Validated: 1
EndChar

StartChar: uniE566
︙
4159
4160
4161
4162
4163
4164
4165
4166

4167
4168
4169
4170
4171
4172


4173
4174
4175
4176
4177
4178
4179
4169
4170
4171
4172
4173
4174
4175

4176
4177
4178
4179
4180


4181
4182
4183
4184
4185
4186
4187
4188
4189







-
+




-
-
+
+







EndChar

StartChar: uniE650
Encoding: 124 58960 124
Width: 1037
Flags: W
HStem: -2 60<361.035 435.033 953.253 1010.82> 0 49<543.265 652.027> 286 36<514.943 580.797> 359 66<58.4784 125.129> 402 58<367.211 444.917> 607 32<177.106 261>
VStem: 0 57<406 505.924> 148 26<463.933 475.584> 200 67<410.241 442> 274.641 84.3586<205.588 329> 425 56<126.64 255.856> 452 64<466.983 558.522> 585 40<194.878 284.873> 675 60<72.5613 225.441> 887 59<79.8865 267.532> 952 60<-0.820206 56.8202>
VStem: 0 57<406 505.924> 148 26<463.933 475.584> 200 67<410.241 442> 275 84<205.648 329> 425 56<126.64 255.856> 452 64<466.983 558.522> 585 40<194.878 284.873> 675 60<72.5613 225.441> 887 59<79.8865 267.532> 952 60<-0.820206 56.8202>
LayerCount: 2
Fore
SplineSet
152 90 m 0x67ef
 221.394397964 119.985233688 274.64143816 164.649883528 274.64143816 208.902553279 c 0
 274.64143816 216.303422646 273.152138825 223.692769088 270 231 c 0
 221 120 275 165 275 209 c 0
 275 216 273 224 270 231 c 0
 252 273 185 332 185 389 c 0
 185 407 200 441 200 442 c 2
 261 607 l 1
 114 601 57 505 57 460 c 0x6fef
 57 436 69 425 89 425 c 0
 110 425 128 443 148 485 c 1
 174 472 l 1
︙
4188
4189
4190
4191
4192
4193
4194
4195
4196


4197
4198
4199
4200
4201
4202
4203
4198
4199
4200
4201
4202
4203
4204


4205
4206
4207
4208
4209
4210
4211
4212
4213







-
-
+
+







 437 460 452 482 452 510 c 0x2fdf
 452 530 431 601 303 608 c 1
 276 542 l 2
 275 538 267 498 267 479 c 0
 267 390 359 361 359 297 c 0
 359 211 296 140 296 140 c 1
 334 114 366 63 394 63 c 0
 399 63 402.9375 62.625 406.671875 62.625 c 0
 417.875 62.625 427.25 66 458 93 c 1
 399 63 403 63 407 63 c 0
 418 63 427 66 458 93 c 1
 438 125 425 152 425 186 c 27
 425 226 431 247 453 276 c 0
 479 311 522 322 563 322 c 0
 599 322 625 289 625 247 c 3
 625 233 625 214 615 195 c 0
 591 148 551 110 526 90 c 1
 550 61 566 49 604 49 c 0x6fef
︙
Changes to modules/MIDI.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// MIDI.js - module to handle the %%MIDI parameters
//
// Copyright (C) 2019-2021 Jean-Francois Moine
// Copyright (C) 2019-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
130
131
132
133
134
135
136
137

138
139

140
141
142
143
144
145
146
147
148
149
150
151

152
153
154
155



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
130
131
132
133
134
135
136

137
138

139
140
141
142
143
144
145
146
147
148
149
150

151
152



153
154
155
156
157
158






159
160
161
162
163
164
165







-
+

-
+











-
+

-
-
-
+
+
+



-
-
-
-
-
-







//				//	M:9/8	fzcfzcfzc
//		break
	case "gchordon":	// %%MIDI gchordon
	case "gchordoff":	// %%MIDI gchordoff
		if (!cfmt.chord)
			cfmt.chord = {}
		if (abc.parse.state >= 2
		 && abc.get_curvoice()) {
		 && curvoice) {
			s = abc.new_block("midigch")
			s.play = true
			s.play = s.invis = 1 //true
			s.on = a[1][7] == 'n'
		} else {
			cfmt.chord.gchon = a[1][7] == 'n'
		}
		break
	case "channel":
		v = parseInt(a[2])
		if (isNaN(v) || v <= 0 || v > 16) {
			abc.syntax(1, abc.errs.bad_val, "%%MIDI channel")
			break
		}
		if (--v != 9) {			// channel range 1..16 => 0..15
		v--				// channel range 1..16 => 0..15
			if (abc.parse.state == 3) {
				s = abc.new_block("midichn");
				s.play = true
				s.chn = v
				s = abc.new_block("midiprog")
				s.play = s.invis = 1 //true
				curvoice.chn = s.chn = v
			} else {
				abc.set_v_param("channel", v)
			}
			break
		}

		// channel 10 is bank 128
		abc2svg.MIDI.do_midi.call(abc, "MIDI control 0 1")	// MSB bank
		abc2svg.MIDI.do_midi.call(abc, "MIDI control 32 0")	// LSB bank
		break
	case "drummap":
//fixme: should have a 'MIDIdrum' per voice?
		v = Number(a[3])
		if (isNaN(v)) {
			abc.syntax(1, abc.errs.bad_val, "%%MIDI drummap")
			break
︙
192
193
194
195
196
197
198
199

200

201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

219
220
221
222
223
224
225
186
187
188
189
190
191
192

193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

213
214
215
216
217
218
219
220







-
+

+

















-
+







		v = parseInt(v)
		if (isNaN(v) || v < 0 || v > 127) {
			abc.syntax(1, abc.errs.bad_val, "%%MIDI program")
			break
		}
		if (abc.parse.state == 3) {
			s = abc.new_block("midiprog");
			s.play = true
			s.play = s.invis = 1 //true
			s.instr = v
			s.chn = curvoice.chn
		} else {
			abc.set_v_param("instr", v)
		}
		break
	case "control":
		n = parseInt(a[2])
		if (isNaN(n) || n < 0 || n > 127) {
			abc.syntax(1, "Bad controller number in %%MIDI")
			break
		}
		v = parseInt(a[3])
		if (isNaN(v) || v < 0 || v > 127) {
			abc.syntax(1, "Bad controller value in %%MIDI")
			break
		}
		if (abc.parse.state == 3) {
			s = abc.new_block("midictl");
			s.play = true
			s.play = s.invis = 1 //true
			s.ctrl = n;
			s.val = v
		} else {
			abc.set_v_param("midictl", a[2] + ' ' + a[3])
		}
		break
	case "temperamentequal":
︙
292
293
294
295
296
297
298
299
300



301
302
303
304


305

306
307


308







309
310
311
312
313
314
315
287
288
289
290
291
292
293


294
295
296
297
298
299
300
301
302

303
304
305
306
307

308
309
310
311
312
313
314
315
316
317
318
319
320
321







-
-
+
+
+




+
+
-
+


+
+
-
+
+
+
+
+
+
+








		break
	}
    }, // do_midi()

    // set the MIDI parameters in the current voice
    set_vp: function(of, a) {
    var	i, item,
	curvoice = this.get_curvoice()
    var	i, item, s,
	abc = this,
	curvoice = abc.get_curvoice()

	for (i = 0; i < a.length; i++) {
		switch (a[i]) {
		case "channel=":		// %%MIDI channel
			s = abc.new_block("midiprog")
			s.play = s.invis = 1 //true
			curvoice.chn = a[++i]
			s.chn = curvoice.chn = a[++i]
			break
		case "instr=":			// %%MIDI program
			s = abc.new_block("midiprog")
			s.play = s.invis = 1 //true
			curvoice.instr = a[++i]
			s.instr = a[++i]
			if (curvoice.chn == undefined) {
				curvoice.chn = curvoice.v < 9 ?
						curvoice.v :
						curvoice.v + 1
			}
			s.chn = curvoice.chn
			break
		case "midictl=":		// %%MIDI control
			if (!curvoice.midictl)
				curvoice.midictl = []
			item = a[++i].split(' ');
			curvoice.midictl[item[0]] = Number(item[1])
			break
︙
Changes to modules/capo.js.
︙
32
33
34
35
36
37
38
39


40

41
42
43
44
45
46
47
32
33
34
35
36
37
38

39
40
41
42
43
44
45
46
47
48
49







-
+
+

+








	if (p_v.capo && a_gch) {
		t = p_v.capo
		i = 0

		while (1) {
			gch = a_gch[i++]
			if (!gch)
			if (!gch) {
				of(s)
				return
			}
			if (gch.type == 'g')
				break
		}
		gch2 = Object.create(gch)
		gch2.capo = false	// (would be erased when setting gch)
		gch2.text = abc.gch_tr1(gch2.text, -abc2svg.ifb40[t % 12])
		if (!p_v.capo_first) {		// if new voice
︙
Changes to modules/combine.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// combine.js - module to add a combine chord line
//
// Copyright (C) 2018-2021 Jean-Francois Moine
// Copyright (C) 2018-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
23
24
25
26
27
28
29

30

















31
32
33
34
35

36
37
38
39
40
41

42
43
44
45
46
47
48
49
23
24
25
26
27
28
29
30

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

52
53
54
55
56
57

58

59
60
61
62
63
64
65







+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




-
+





-
+
-







//	%%voicecombine n	'n' is the combine level

abc2svg.combine = {

    // function called at start of the generation when multi-voices
    comb_v: function() {
    var	C = abc2svg.C,
	abc = this
	sy

    // get the symbol of the note to combine
    function get_cmb(s) {
    var	p,
	s2 = s.ts_next,
	i = s.p_v.id.indexOf('.')		// '.' is the group separator

	if (i >= 0) {
		p = s.p_v.id.slice(0, i)	// group
		while (s2 && s2.time == s.time) {
			if (s2.p_v.id.indexOf(p) == 0)
				break
			s2 = s2.ts_next
		}
	}
	return s2
    } // get_cmb()

    // check if voice combine may occur
    function may_combine(s) {
    var	nhd2,
	s2 = s.ts_next
	s2 = get_cmb(s)

	if (!s2 || (s2.type != C.NOTE && s2.type != C.REST))
		return false
	if (s2.st != s.st
	 || s2.time != s.time
	 || s2.dur != s.dur
	 || s2.dur != s.dur)
	 || sy.voices[s2.v].range != sy.voices[s.v].range + 1)	// next voice only
		return false
	if (s.combine <= 0
	 && s2.type != s.type)
		return false
//	if (s2.a_dd) { //fixme: should check the double decorations
//		return false
//	}
︙
64
65
66
67
68
69
70
71

72

73
74
75





76
77
78
79

80
81
82
83
84
85
86
80
81
82
83
84
85
86

87
88
89



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106







-
+

+
-
-
-
+
+
+
+
+




+







	 && s.notes[0].pit <= s2.notes[nhd2].pit + 1)
		return false
	return true
    } // may_combine()

    // combine two notes
    function combine_notes(s, s2) {
    var	nhd, type, m;
    var	nhd, type, m, not

	// put the notes of the 2nd voice into the 1st one
	for (m = 0; m <= s2.nhd; m++)	// change the container of the notes
		s2.notes[m].s = s
	Array.prototype.push.apply(s.notes, s2.notes);
	for (m = 0; m <= s2.nhd; m++) {
		not = abc.clone(s2.notes[m])
		not.noplay = true	// and don't play it
		s.notes.push(not)
	}
	s.nhd = nhd = s.notes.length - 1;
	s.notes.sort(abc2svg.pitcmp)	// sort the notes by pitch

	if (s.combine >= 3) {		// remove unison heads
//fixme: KO for playback
		for (m = nhd; m > 0; m--) {
			if (s.notes[m].pit == s.notes[m - 1].pit
			 && s.notes[m].acc == s.notes[m - 1].acc)
				s.notes.splice(m, 1)
		}
		s.nhd = nhd = s.notes.length - 1
	}
︙
94
95
96
97
98
99
100

101
102

103
104

105
106
107
108
109
110


111
112
113
114
115
116
117
118
119
120
121
122
123
124

125
126
127
128
129
130



131
132
133
134
135





136
137
138















139
140
141
142
143
144
145
146
147
148


149
150
151
152
153
154

155
156
157
158
159
160


161
162
163
164

165
166
167
168
169
170
171
172
173
174
175
176
177
178

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193

194
195
196
197
198
199
200
114
115
116
117
118
119
120
121
122

123
124

125
126
127
128
129


130
131
132


133
134
135
136
137
138
139
140
141
142

143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186

187
188
189
190
191
192
193

194
195
196
197
198


199
200




201
202
203
204
205
206
207
208
209
210
211
212
213
214

215
216
217
218
219
220
221
222
223
224
225
226
227
228
229

230
231
232
233
234
235
236
237







+

-
+

-
+




-
-
+
+

-
-










-
+






+
+
+





+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









-
+
+





-
+




-
-
+
+
-
-
-
-
+













-
+














-
+







		s.notes[0].tie_ty = C.SL_BELOW | (type & C.SL_DOTTED);
	type = s.notes[nhd].tie_ty
	if ((type & 0x07) == C.SL_AUTO)
		s.notes[nhd].tie_ty = C.SL_ABOVE | (type & C.SL_DOTTED)
} // combine_notes()

// combine 2 voices
// return the remaining one
function do_combine(s) {
	var s2, nhd, nhd2, type
	var s2, s3, type, i, n, sl

		s2 = s.ts_next
		s2 = get_cmb(s)

		// there may be more voices
		if (!s.in_tuplet
		 && s2.combine != undefined && s2.combine >= 0
		 && may_combine.call(this, s2))
			do_combine.call(this, s2)
		 && may_combine(s2))
			s2 = do_combine(s2)

		nhd = s.nhd;
		nhd2 = s2.nhd
		if (s.type != s2.type) {	// if note and rest
			if (s2.type != C.REST) {
				s2 = s;
				s = s2.ts_next
			}
		} else if (s.type == C.REST) {
			if (s.invis
			 && !s2.invis)
				delete s.invis
		} else {
			combine_notes.call(this, s, s2)
			combine_notes(s, s2)
			if (s2.ti1)
				s.ti1 = true
			if (s2.ti2)
				s.ti2 = true
		}

		// if some slurs start on the second symbol
		// move them to the combined symbol
		// also, set a flag in the symbols of the ending slurs
		if (s2.sls) {
			if (s.sls)
				Array.prototype.push.apply(s.sls, s2.sls)
			else
				s.sls = s2.sls
			for (i = 0; i < s2.sls.length; i++) {
				sl = s2.sls[i]
				if (sl.se)
					sl.se.slsr = s	// reverse pointer
				sl.ty = C.SL_BELOW
		}
		if (s2.sl1)
			s.sl1 = true
			}
			delete s2.sls
		}

		// if a combined slur is ending on the second symbol,
		// update its starting symbol
		s3 = s2.slsr			// pointer to the starting symbol
		if (s3) {
			for (i = 0; i < s3.sls.length; i++) {
				sl = s3.sls[i]
				if (sl.se == s2)
					sl.se = s
			}
		}

		if (s2.a_gch)
			s.a_gch = s2.a_gch
		if (s2.a_dd) {
			if (!s.a_dd)
				s.a_dd = s2.a_dd
			else
				Array.prototype.push.apply(s.a_dd, s2.a_dd)
		}

		this.unlksym(s2)			// remove the next symbol
		s2.play = s2.invis = true	// don't display, but play
		return s
} // do_combine()

	// code of comb_v()
	var s, s2, g, i, r

	for (s = this.get_tsfirst(); s; s = s.ts_next) {
	for (s = abc.get_tsfirst(); s; s = s.ts_next) {
		switch (s.type) {
		case C.REST:
			if (s.combine == undefined || s.combine < 0)
				continue
			if (may_combine.call(this, s))
				do_combine.call(this, s)
			if (may_combine(s))
				s = do_combine(s)
			continue
		case C.STAVES:
			sy = s.sy
			// fall thru
//			continue		// fall thru
		default:
			continue
		case C.NOTE:
			if (s.combine == undefined || s.combine <= 0)
				continue
			break
		}

		if (!s.beam_st)
			continue

		s2 = s
		while (1) {
			if (!may_combine.call(this, s2)) {
			if (!may_combine(s2)) {
				s2 = null
				break
			}
//fixme: may have rests in beam
			if (s2.beam_end)
				break
			do {
				s2 = s2.next
			} while (s2.type != C.NOTE && s2.type != C.REST)
		}
		if (!s2)
			continue
		s2 = s
		while (1) {
			do_combine.call(this, s2)
			s2 = do_combine(s2)
//fixme: may have rests in beam
			if (s2.beam_end)
				break
			do {
				s2 = s2.next
			} while (s2.type != C.NOTE && s2.type != C.REST)
		}
︙
Changes to modules/diag.js.
︙
167
168
169
170
171
172
173
174

175
176
177
178
179
180
181
167
168
169
170
171
172
173

174
175
176
177
178
179
180
181







-
+







				+ '" y="' + ((l[1] || 1) * 6 - 35)
				+ '" class="frn">' + l[0] + '</text>\n'
		decos[nm] = "3 " + nm + " 40 " + (l[0] ? "30" : "10") + " 0"
		// fingers
		dc += '<text x="' + (n == 6 ? '-12,-8,-4,0,4,8'
					: '-8,-4,0,4')
				+ '" y="-36" class="fng">'
				+ d[2].replace(/[y0]/g, ' ')
				+ d[2].replace(/[y0]/g, ' ')
				+ '</text>\n'
		// dots
		for (i = 0; i < n; i++) {
			l = d[0][i]
			if (l && l != 'x' && l != '0')
				dc += '<use x="' + (i * 4 + x)
					+ '" y="' + (l * 6 - 37)
︙
Changes to modules/equalbars.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// equalbars.js - module to set equal spaced measure bars
//
// Copyright (C) 2018-2021 Jean-Francois Moine
// Copyright (C) 2018-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
27
28
29
30
31
32
33
34
35


36
37
38






39
40
41

42
43
44
45
46
47
48
49
50
51
52
53

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

72
73
74
75
76
77
78
79

80
81
82
83

84
85
86
87
88
89
90















91
92
93
94
95
96
97
98
99
100
101





102
103
104
105
106
107
108
27
28
29
30
31
32
33


34
35



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

75
76
77
78
79
80
81
82

83
84
85
86

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132







-
-
+
+
-
-
-
+
+
+
+
+
+



+











-
+

















-
+







-
+



-
+







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+











+
+
+
+
+







    // new tune - clear the width of the start of the staff
    output_music: function(of) {
	this.equalbars_d = 0;
	of()
    },

    // get the equalbars parameter
    set_fmt: function(of, cmd, param) {
	if (cmd == "equalbars")
   set_fmt: function(of, cmd, parm) {
	if (cmd != "equalbars") {
		this.cfmt().equalbars = this.get_bool(param)
	else
		of(cmd, param)
		of(cmd, parm)
		return
	}
    var	fmt = this.cfmt()
	fmt.equalbars = this.get_bool(parm)
	fmt.stretchlast = 1
    },

    // adjust the symbol offsets of a music line
    // only the bars of the first voice are treated
    set_sym_glue: function(of, width) {
    var	C = abc2svg.C,
	s, s2, w, i, n, x, g, t, t0,
	bars = [],
	tsfirst = this.get_tsfirst();

	of(width)			// compute the x offset of the symbols
	if (!this.cfmt().equalbars)
		return

	// search the first note/rest/space
	for (s2 = tsfirst; s2; s2 = s2.ts_next) {
	for (s2 = tsfirst; s2; s2 = s2.next) {
		switch (s2.type) {
		default:
			continue
		case C.GRACE:
		case C.MREST:
		case C.NOTE:
		case C.REST:
		case C.SPACE:
			break
		}
		break
	}
	if (!s2)
		return

	// build an array of the bars
	t0 = t = s2.time
	for (s = s2; s.ts_next; s = s.ts_next) {
	for (s = s2; s.next; s = s.next) {
		if (s.type == C.BAR && s.seqst) {
			bars.push([s, s.time - t]);
			t = s.time
		}
	}

	// push the last bar if it is not the invisible bar after a key change
	if (!s.invis || s.prev.type != C.KEY)
	if (!s.invis || (s.prev && s.prev.type != C.KEY))
		bars.push([s, s.time - t])
	else
		bars[bars.length - 1][0] = s	// replace the last bar
	width = s.x

	t = s.time
	if (s.dur)
		t += s.dur;

	n = bars.length
	if (n <= 1)
		return				// one or no bar

	// if small width, get the widest measure
	if (s.x < width) {
		w = 0
		x = 0
		for (i = 0; i < n; i++) {
			s = bars[i][0]
			if (s.x - x > w)
				w = s.x - x
			x = s.x
		}
		if (w * n < width)
			width = w * n
		this.set_realwidth(width)
	}

	// set the measure parameters
	x = s2.type == C.GRACE ? s2.extra.x : s2.x;
	if (this.equalbars_d < x)
		this.equalbars_d = x		// new offset of the first note/rest

	d = this.equalbars_d
	w = (width - d) / (t - t0)		// width per time unit

	// loop on the bars
	for (i = 0; i < n; i++) {
		do {				// don't shift the 1st note
			s2.x = d + s2.x - x	// from the bar
			s2 = s2.ts_next
		} while (!s2.seqst)

		s = bars[i][0];			// next bar
		f = w * bars[i][1] / (s.x - x)

		// and update the x offsets
		for ( ; s2 != s; s2 = s2.ts_next) {
			if (s2.type == C.GRACE) {
				for (g = s2.extra; g; g = g.next)
︙
Changes to modules/gamelan.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// gamelan.js - module to output Gamelan (indonesian) music sheets
//
// Copyright (C) 2020-2021 Jean-Francois Moine
// Copyright (C) 2020-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
165
166
167
168
169
170
171
172

173
174
175
176
177
178
179
165
166
167
168
169
170
171

172
173
174
175
176
177
178
179







-
+







	} // do_tie()

	function set_sym(p_v) {
	    var s, s2, note, pit, nn, p, a, m, i,
		sf = p_v.key.k_sf,
		delta = abc2svg.gamelan.cgd2cde[sf + 7] - 2

		p_v.key.k_a_acc = []	// no accidental
		delete p_v.key.k_a_acc		// no accidental

		// no (visible) clef
		p_v.clef.invis = true

		// scan the voice
		for (s = p_v.sym; s; s = s.next) {
			s.st = p_v.st
︙
354
355
356
357
358
359
360

361
362
363
364
365
366
367
368
369
370





















371
372
373
374
375
376
377


378
379
380
381
382
383
384
385
386
354
355
356
357
358
359
360
361










362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382







383
384


385
386
387
388
389
390
391







+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
-
-







			continue
		switch (s.type) {
		case C.NOTE:
		case C.REST:
			x = s.x
			y = staff_tb[s.st].y
			draw_hd(s, x, y)
			break

			if (s.nflags > 0) {
				if (s.beam_st) {
					nl = s.nflags
					s2 = s
					while (1) {
						if (s2.nflags && s2.nflags > nl)
							nl = s2.nflags
						if (s2.beam_end)
							break
		}
	}

	// draw the (pseudo) beams
	for (s = p_voice.sym; s; s = s.next) {
		if (s.invis)
			continue
		switch (s.type) {
		case C.NOTE:
		case C.REST:
			nl = s.nflags
			if (nl <= 0)
				continue
			y = staff_tb[s.st].y
			s2 = s
			while (s.next && s.next.nflags > 0) {
				s = s.next
				if (s.nflags > nl)
					nl = s.nflags
				if (s.beam_end)
					break
						if (!s2.next
						 || !s2.next.nflags
						 || s2.next.nflags <= 0)
							break
						s2 = s2.next
					}
					draw_dur(s, y + 7, s2, 1, nl)
			}
			draw_dur(s2, y + 7, s, 1, nl)
				}
			}
			break
		}
	}
  }, // draw_symbols()

// set some parameters
    set_fmt: function(of, cmd, param) {
︙
434
435
436
437
438
439
440
441

442
443
444
445
446
447
448
439
440
441
442
443
444
445

446
447
448
449
450
451
452
453







-
+







    var	w, m, note,
	C = abc2svg.C

	switch (s.type) {
	case C.CLEF:
	case C.KEY:
	case C.METER:
		s.wl = s.wr = 0
		s.wl = s.wr = .1		// (must not be null)
		break
	}
    }, // set_width()

    set_hooks: function(abc) {
	abc.do_pscom = abc2svg.gamelan.do_pscom.bind(abc, abc.do_pscom)
	abc.draw_symbols = abc2svg.gamelan.draw_symbols.bind(abc, abc.draw_symbols)
︙
Changes to modules/grid.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - grid.js - module to insert a chord grid before or after a tune
//
// Copyright (C) 2018-2021 Jean-Francois Moine
// Copyright (C) 2018-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
43
44
45
46
47
48
49
50

51
52
53
54
55
56
57
43
44
45
46
47
48
49

50
51
52
53
54
55
56
57







-
+







    var	abc = this,
	img, cls,
	cfmt = abc.cfmt(),
	grid = cfmt.grid

// generate the grid
function build_grid(s, font) {
    var	i, k, l, nr, bar, w, hr, x0, x, y, yl, ps,
    var	i, k, l, nr, bar, w, hr, x0, x, y, yl, ps, d,
	lc = '',
	chords = s.chords,
	bars = s.bars,
	parts = s.parts || [],
	wmx = s.wmx,
	cells = [],
	nc = grid.n
︙
123
124
125
126
127
128
129




















































130
131
132
133
134
135
136
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







			  }
			}
		} else {
			abc.set_font('grid')
			abc.xy_str(x, y, cell[0])
		}
	} // build_cell()

	// draw the horizontal lines
	function draw_hl() {
	    var	i, i1, j, x,
		y = -1

		for (i = 0; i <= nr + 1; i++) {
			j = 0
			i1 = i > 0 ? i - 1 : 0
			while (1) {
				while (j <= nc && !d[i1][j])
					j++
				if (j > nc)
					break
				x = wmx * j
				while (j <= nc && d[i1][j])
					j++
				if (i && i1 < nr) {
					while (j <= nc && d[i1 + 1][j])
						j++
				}
				abc.out_svg('M')
				abc.out_sxsy(x0 + x, ' ', y)
				abc.out_svg('h' + (wmx * j - x).toFixed(1)+ '\n')
			}
			y -= hr
		}
	} // draw_hl()

	// draw the vertical lines
	function draw_vl() {
	    var	i, i1, j, y,
		x = x0

		for (i = 0; i <= nc; i++) {
			j = 0
			i1 = i > 0 ? i - 1 : 0
			while (1) {
				while (j <= nr && !d[j][i1])
					j++
				if (j > nr)
					break
				y = hr * j
				while (j <= nr && d[j][i1])
					j++
				abc.out_svg('M')
				abc.out_sxsy(x, ' ', -y - .5)
				abc.out_svg('v' + (hr * j - y + 1).toFixed(1) + '\n')
			}
			x += wmx
		}
	} // draw_vl()

	// ------- build_grid() -------

	// set some chords in each cell
	set_chords()

	// build the content of the cells
︙
176
177
178
179
180
181
182
183

184

185
186
187
188
189
190
191
192
193
194
195

196

197
198
199
200
201
202
203
204
205
206
207
208
209

210
211
212
213
214
215
216

217
218
219
220
221
222
223
224
228
229
230
231
232
233
234

235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258






259







260

261
262
263
264
265
266
267







-
+

+











+

+







-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-







		nc /= 2;
		w /= 2
	}

	// generate the cells
	yl = -1
	y = -1 + font.size * .6
	nr = 0
	nr = -1
	x0 = (x0 / cfmt.scale - w) / 2
	d = []
	for (i = 0; i < cells.length; i++) {
		if (i == 0
		 || (grid.repbrk
		  && (bars[i].slice(-1) == ':' || bars[i][0] == ':'))
		 || parts[i]
		 || k >= nc) {
			y -= hr			// new row
			yl -= hr
			x = x0 + wmx / 2
			k = 0
			nr++
			d[nr] = []
		}
		d[nr][k] = 1
		k++
		build_cell(cells[i], x, y, yl, hr)
		x += wmx
	}

	// draw the lines
	abc.out_svg('<path class="stroke" stroke-width="1" d="\n')
	y = -1
	for (i = 0; i <= nr; i++) {
		abc.out_svg('M')
		abc.out_sxsy(x0, ' ', y)
		abc.out_svg('h' + w.toFixed(1)+ '\n')
		y -= hr
	draw_hl()
	}
	x = x0
	for (i = 0; i <= nc; i++) {
		abc.out_svg('M')
		abc.out_sxsy(x, ' ', -1)
		abc.out_svg('v' + (hr * nr).toFixed(1) + '\n')
		x += wmx
	draw_vl()
	}
	abc.out_svg('"/>\n')

	// show the repeat signs and the parts
	y = -1 + font.size * .7
	x = x0
	for (i = 0; i < bars.length; i++) {
		bar = bars[i]
︙
249
250
251
252
253
254
255
256

257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
292
293
294
295
296
297
298

299
300
301
302
303
304
305
306
307


308
309
310
311
312
313
314







-
+








-
-







			abc.out_svg('<text class="' + cls + '" x="')
			abc.out_sxsy(x + 5, '" y="', y)
			abc.out_svg('" style="font-weight:bold;font-size:' +
				(font.size * 1.5).toFixed(1) + 'px">:</text>\n')
		}
		x += wmx
	}
	abc.vskip(hr * nr + 6)
	abc.vskip(hr * (nr + 1) + 6)
} // build_grid()

	// ----- block_gen() -----
    var	p_voice, n, font, f2

	img = abc.get_img()

	// set the text style
	if (!cfmt.gridfont)
		abc.param_set_font("gridfont", "serif 16")
	font = abc.get_font('grid')
	if (font.class)
		font.class += ' mid'
	else
		font.class = 'mid'
	cls = abc.font_class(font)

︙
289
290
291
292
293
294
295
296


























297
298
299
300
301
302
303
330
331
332
333
334
335
336

337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369







-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    }, // block_gen()

    set_stems: function(of) {
    var	C = abc2svg.C,
	abc = this,
	tsfirst = abc.get_tsfirst(),
	voice_tb = abc.get_voice_tb(),
	grid = abc.cfmt().grid
	cfmt = abc.cfmt(),
	grid = cfmt.grid

	// extract one of the chord symbols
	// With chords as "[yyy];xxx"
	// (!sel - default) returns "yyy" and (sel) returns "xxx"
	function cs_filter(a_cs) {
	    var	i, cs, t

		for (i = 0; i < a_cs.length; i++) {
			cs = a_cs[i]
			if (cs.type != 'g')
				continue
			t = cs.text
			if (cfmt.altchord) {
				for (i++; i < a_cs.length; i++) {
					cs = a_cs[i]
					if (cs.type != 'g')
						continue
					t = cs.text
					break
				}
			}
			return t.replace(/\[|\]/g, '')
		}
	} // cs_filter()

	function get_beat(s) {
	    var	beat = C.BLEN / 4

		if (!s.a_meter[0] || s.a_meter[0].top[0] == 'C'
		 || !s.a_meter[0].bot)
			return beat
︙
333
334
335
336
337
338
339
340

341
342
343
344
345
346
347
348
349
350
351
352







353
354
355
356
357
358
359
399
400
401
402
403
404
405

406

407
408









409
410
411
412
413
414
415
416
417
418
419
420
421
422







-
+
-


-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+







			if (s.part)
				parts[chords.length] = s.part.text
			switch (s.type) {
			case C.NOTE:
			case C.REST:
				if (!s.a_gch || chord[beat_i])
					break
				bt = abc2svg.cs_filter(s.a_gch,
				bt = cs_filter(s.a_gch)
							abc.cfmt().altchord)
				if (!bt)
					break
				chord[beat_i] = bt
				for (i = 0; i < s.a_gch.length; i++) {
					if (s.a_gch[i].type == 'g')
						break
				}
				abc.set_font(s.a_gch[i].font)
				w = abc.strwh(bt)[0]
				if (w > wmx)
					wmx = w
					w = abc.strwh(bt.replace(
						/<[^>]+>/gm,''))
					if (w[0] > wmx)
						wmx = w[0]
					bt = new String(bt)
					bt.wh = w
				chord[beat_i] = bt
				break
			case C.BAR:
				i = s.bar_num		// check if normal measure bar
				bt = s.bar_type
				while (s.ts_next && s.ts_next.time == s.time) {
					if (s.ts_next.dur)
						break
︙
437
438
439
440
441
442
443



444
445
446
447
448
449
450
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516







+
+
+







			dur: 0,
			time: 0,
			p_v: p_v,
			v: p_v.v,
			st: p_v.st
		}

		if (!cfmt.gridfont)
			abc.param_set_font("gridfont", "serif 16")
		abc.set_font('grid')
		build_chords(s)			// build the array of the chords

		// and insert it in the tune
		if (!s.chords) {		// if no chord
			;
		} else if (grid.nomusic) {	// if just the grid
			this.set_tsfirst(s)
︙
Changes to modules/grid2.js.
1
2
3

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

25
26
27
28
29
30

31
32





33
34
35
36


37
38


39
40
41
42
43
44
45
46
47
48
49
50

51

52
53
54
55
56
57
58

59
60
61
62
63
64

65
66
67
68
69
70
71
1
2

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

24
25
26
27
28
29

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

60
61
62
63
64
65
66

67
68
69
70
71
72

73
74
75
76
77
78
79
80


-
+




















-
+





-
+


+
+
+
+
+




+
+

-
+
+












+
-
+






-
+





-
+







// grid2.js - module to replace a voice in the music by a chord grid
//
// Copyright (C) 2018-2021 Jean-Francois Moine
// Copyright (C) 2018-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg.  If not, see <http://www.gnu.org/licenses/>.
//
// This module is loaded when "%%grid2" appears in a ABC source.
//
// Parameters
//	%%grid2 y
// This command must appear in a voice.
// This command may appear globally or in a voice.

abc2svg.grid2 = {

// function called before tune generation
    do_grid: function() {
    var s, v, p_v, ix, cs, c_a_cs, bt,
    var s, v, p_v, ix, cs, c_a_cs, bt, gch,
	voice_tb = this.get_voice_tb()

	if (this.cfmt().grid2)
		for (v = 0; v < voice_tb.length; v++)
			if (voice_tb[v].grid2 == undefined)
				voice_tb[v].grid2 = 1

	for (v = 0; v < voice_tb.length; v++) {
		p_v = voice_tb[v]
		if (!p_v.grid2)
			continue
		curvoice = p_v
		this.set_v_param("stafflines", "...")	// no staff
		p_v.clef.invis = true;		// no clef
		p_v.key.k_sf = p_v.key.k_a_acc = 0; // no key signature
		p_v.key.k_sf = 0		// no key signature
		delete p_v.key.k_a_acc
		p_v.staffnonote = 2		// draw the staff
		for (s = p_v.sym; s; s = s.next) {
			delete s.a_dd		// no decoration
			if (!s.dur) {
				if (s.bar_type)
					bt = s.time
				continue
			}

			// set all notes
				s.invis = true;	//  as invisible
				delete s.sl1;	//  with no slur
				delete s.ti1	//  and no tie
				delete s.tie_s	//  and no tie
				delete s.ti2
				for (ix = 0; ix <= s.nhd; ix++)
					delete s.notes[ix].tie_ty
				if (s.tf)	// don't show the tuplets
					s.tf[0] = 1
				if (!s.a_gch) {
					if (s.time == bt)
						s.a_gch = c_a_cs
						s.a_gch = [ this.clone(c_a_cs) ]
					continue
				}
				for (ix = 0; ix < s.a_gch.length; ix++) {
					gch = s.a_gch[ix]
					if (gch.type == 'g') {
						c_a_cs = s.a_gch
						c_a_cs = gch
						break
					}
				}
		}
	}
    }, // do_grid()

︙
89
90
91
92
93
94
95
96
97




98
99

100


101
102
103
104
105
106
107
98
99
100
101
102
103
104


105
106
107
108


109

110
111
112
113
114
115
116
117
118







-
-
+
+
+
+
-
-
+
-
+
+







    output_music: function(of) {
	abc2svg.grid2.do_grid.call(this);
	of()
    },

    set_fmt: function(of, cmd, param) {
	if (cmd == "grid2") {
	    var	curvoice = this.get_curvoice()
		if (curvoice) {
	    var	curvoice = this.get_curvoice(),
		v = this.get_bool(param)

		if (curvoice)
			this.set_v_param("stafflines", "...");	// no staff lines
			curvoice.grid2 = param
			curvoice.grid2 = v
		}
		else
			this.cfmt().grid2 = v
		return
	}
	of(cmd, param)
    },

    set_hooks: function(abc) {
	abc.draw_gchord = abc2svg.grid2.draw_gchord.bind(abc, abc.draw_gchord);
︙
Changes to modules/grid3.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// grid3.js - module to insert a manual chords
//
// Copyright (C) 2020-2021 Jean-Francois Moine
// Copyright (C) 2020-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
35
36
37
38
39
40
41


42
43
44
45

46
47
48
49
50
51
52
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55







+
+




+








// generate the grid
    block_gen: function(of, s) {
	if (s.subtype != "grid3") {
		of(s)
		return
	}

	this.blk_flush()

    var	abc = this,
	cfmt = abc.cfmt(),
	img = abc.get_img(),
	posy = abc.get_posy(),
	txt = s.text,
	font, font_cl, cls, w,
	ln, i,
	lines = [],
	cl = [],
	bars = [],
	cells = [],
︙
114
115
116
117
118
119
120
121
122


123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142

143
144
145
146
147
148

149
150


151
152
153
154
155
156
157
158
159

160
161
162
163
164
165
166
117
118
119
120
121
122
123


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

145
146
147
148
149
150
151
152
153

154
155
156
157
158
159
160
161
162
163

164
165
166
167
168
169
170
171







-
-
+
+



















-
+






+

-
+
+








-
+







		// build the content of the cells
		hr = font.size * 2.1
		if (wc < hr * 1.4)
			wc = hr * 1.4			// cell width
		w = wc * nc				// grid width

		// generate the cells
		yl = 3
		y = 3 - font.size * .7
		yl = posy + 3
		y = posy + 3 - font.size * .7
		x0 = (img.width / cfmt.scale - w) / 2
		while (1) {
			cl = cells.shift()
			if (!cl)
				break
			y += hr
			yl += hr
			x = x0 + wc / 2
			while (1) {
				cell = cl.shift()
				if (!cell)
					break
				lc += build_cell(cell, x, y, yl, hr)
				x += wc
			}
		}

		// draw the lines
		line = '<path class="stroke" d="\n'
		y = 3
		y = posy + 3
		for (i = 0; i <= nr; i++) {
			line += 'M' + x0.toFixed(1) + ' ' + y.toFixed(1) +
				'h' + w.toFixed(1)+ '\n'
			y += hr
		}
		x = x0
		y = posy + 3
		for (i = 0; i <= nc; i++) {
			line += 'M' + x.toFixed(1) + ' 3v' + (hr * nr).toFixed(1) + '\n'
			line += 'M' + x.toFixed(1) + ' ' + y.toFixed(1) +
				'v' + (hr * nr).toFixed(1) + '\n'
			x += wc
		}
		line += '"/>\n'

		// insert the cells
		line += lc

		// show the repeat signs
		y = 3 - font.size * .7
		y = posy + 3 - font.size * .7
		while (1) {
			bl = bars.shift()
			if (!bl)
				break
			x = x0
			y += hr
			while (1) {
︙
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
255
256
257
258
259
260
261

262
263
264
265
266
267
268







-







				wc = w
		}
		nr++
	}
	wc += abc.strwh('  ')[0]

	// build the grid and insert it in the music
	abc.blk_flush()
	build_grid()
	abc.blk_flush()
    }, // block_gen()

// handle %%begingrid
    do_begin_end: function(of, type, opt, txt) {
    var	vt = this.get_voice_tb()
︙
Changes to modules/jazzchord.js.
︙
39
40
41
42
43
44
45
46

47
48
49
50
51
52



















53
54
55
56
57
58
59
39
40
41
42
43
44
45

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78







-
+






+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







//	"M": "Δ",
//	"min": "–",
//	"m": "–",
	"^": "∆"
    },

    gch_build: function(of, s) {
    var	gch, ix, t,
    var	gch, ix, r, t,
	fmt = s.fmt

	if (!fmt.jazzchord) {
		of(s)
		return
	}

	// jazzify a chord
	function jzch(t) {
	    var r = '',
		a = t.match(/(\[?[A-G])([#♯b♭]?)([^/]*)\/?(.*)\)?/)
		// a[1]=note, a[2]=acc, a[3]=quality, a[4]=bass

		if (!a)
			return t
		if (a[2])
			r = "$7" + a[2]
		if (a[3])
			r += (a[2] ? "$0" : '') + "$8" + a[3]
		if (a[4])
			r += ((a[2] || a[3]) ? "$0" : '') + "$9/" + a[4]
		if (!r)
			return t
		return a[1] + r + "$0"
	} // jzch()

	for (ix = 0; ix < s.a_gch.length; ix++) {
		gch = s.a_gch[ix]
		t = gch.text
		if (gch.type != 'g'
		 || t.indexOf('$') >= 0)	// if some formatting already
			continue
︙
69
70
71
72
73
74
75
76

77
78
79
80
81
82
83




84
85

86
87
88

89
90

91
92
93
94
95
96
97
88
89
90
91
92
93
94

95







96
97
98
99


100
101
102

103
104

105
106
107
108
109
110
111
112







-
+
-
-
-
-
-
-
-
+
+
+
+
-
-
+


-
+

-
+







						return fmt.jzrep[x]
			})
		}

		if (fmt.jazzchord == 1) {
			if (t[0] == '(')
				t = t.slice(1, -1)
			a = t.match(/([A-G])([#♯b♭]?)([^/]*)\/?(.*)/)
			t = t.split('(')	// possible "ch1(ch2)"
			// a[1]=note, a[2]=acc, a[3]=quality, a[4]=bass
			if (!a)
				continue
			t = a[1] + "$7" + (a[2] || '')
			if (a[3])
				t += "$8" + a[3]
			if (a[4])
			r = jzch(t[0])
			if (t.length > 1)
				r += '(' + jzch(t[1])
		} else {
				t += "$0/$9" + a[4]
			t += "$0"
			r = t
		}
		if (gch.text[0] == '(')
			gch.text = '(' + t + ')'
			gch.text = '(' + r + ')'
		else
			gch.text = t
			gch.text = r
	}
	of(s)				// build the chord symbols
    }, // gch_build()

    set_fmt: function(of, cmd, parm) {
    var	i, k, s,
	cfmt = this.cfmt()
︙
Changes to modules/jianpu.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// jianpu.js - module to output jiănpŭ (简谱) music sheets
//
// Copyright (C) 2020-2022 Jean-Francois Moine
// Copyright (C) 2020-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
31
32
33
34
35
36
37
38

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

57

58
59
60
61




62
63
64
65



































66
67
68

69
70
71
72
73














74
75
76

77
78
79
80
81
82
83
84
85
86
87
88
89
90
91





92
93
94
95
96






97
98
99
100
101
102


103
104
105
106
107
108
109

110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

125
126

127
128
129
130
131
132
133
31
32
33
34
35
36
37

38
39
40
41
42












43
44
45

46
47
48
49
50
51
52
53
54




55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93





94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131





132
133
134
135
136
137

138
139
140
141

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

169
170
171
172
173
174
175
176







-
+




-
-
-
-
-
-
-
-
-
-
-
-


+
-
+




+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+















+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
-




-
+
+







+















+

-
+







  cgd2cde: new Int8Array([0, -4, -1, -5, -2, -6, -3,
			  0, -4, -1, -5, -2, -6, -3, 0]),
  acc2: new Int8Array([-2, -1, 3, 1, 2]),
  acc_tb: ["\ue264", "\ue260", , "\ue262", "\ue263", "\ue261"],

// don't calculate the beams
  calc_beam: function(of, bm, s1) {
	if (!this.cfmt().jianpu)
	if (!s1.p_v.jianpu)
		return of(bm, s1)
//	return 0
  }, // calc_beam()

// change %%staves and %%score
  do_pscom: function(of, p) {
    if (this.cfmt().jianpu)
	switch (p.match(/\w+/)[0]) {
	case 'staves':
	case 'score':
		p = p.replace(/\(|\)/g, '')
		break
	}
	of(p)
  },

// adjust some symbols before the generation
  output_music: function(of) {
    var	p_v, v,
    var	C = abc2svg.C,
	C = abc2svg.C,
	abc = this,
	cur_sy = abc.get_cur_sy(),
	voice_tb = abc.get_voice_tb()

	// handle the overlay voices
	function ov_def(v) {
	    var	s1, tim,
		s = p_v.sym
	if (!abc.cfmt().jianpu) {
		of()
		return
	}

		while (s) {
			s1 = s.ts_prev
			if (!s.invis
			 && s.dur
			 && s1.v != v
			 && s1.st == s.st	// overlay start
			 && s1.time == s.time) {
				while (1) {	// go back to the previous bar
					if (!s1.prev
					 || s1.prev.bar_type)
						break
					s1 = s1.prev
				}
				//add deco '{' on s1
				while (!s1.bar_type) {
					s1.dy = 14
					s1.notes[0].pit = 30
					s1 = s1.next
				}
				// add deco '}' on s1

				while (1) {
					s.dy = -14
					s.notes[0].pit = 20
					if (!s.next
					 || s.next.bar_type
					 || s.next.time >= s1.time)
						break
					s = s.next
				}
			}
			s = s.next
		}
	} // ov_def()

	// output the key and time signatures
	function set_head() {
	    var	v, p_v, mt, s2, sk, s,
	    var	tsfirst = abc.get_tsfirst(),
		p_v = voice_tb[0],
		mt = p_v.meter.a_meter[0],
		sk = p_v.key,
		s2 = voice_tb[0].sym,
		tsfirst = abc.get_tsfirst()

		// search a jianpu voice
		for (v = 0; v < voice_tb.length; v++) {
			p_v = voice_tb[v]
			if (p_v.jianpu)
				break
		}
		if (v >= voice_tb.length)
				return

		mt = p_v.meter.a_meter[0]
		sk = p_v.key
		s2 = p_v.sym
		s = {
			type: C.BLOCK,
			subtype: "text",
			time: s2.time,
			dur: 0,
			v: 0,
			p_v: p_v,
			st: 0,
			fmt: s2.fmt,
			seqst: true,
			text: (sk.k_mode + 1) + "=" +
				(abc2svg.jianpu.k_tb[sk.k_sf + 7 +
					abc2svg.jianpu.cde2fcg[sk.k_mode]]),
			font: abc.get_font("text")
		}

		if (mt)
			s.text += ' ' + (mt.bot ? (mt.top + '/' + mt.bot) : mt.top)

		// insert the block after the first %%staves
		s2 = tsfirst
		s.next = s2.next
		if (s.next)
			s.next.prev = s
		s2.prev = s
		s.next = s2
		voice_tb[0].sym = s
		tsfirst.ts_prev = s
		s.ts_next = tsfirst
		s.prev = s2
		s2.next = s
		s.ts_next = s2.ts_next
		s.ts_next.ts_prev = s
		s.ts_prev = s2
		s2.ts_next = s
		abc.set_tsfirst(s)
	} // set head()

	// expand a long note/rest
	function slice(s) {
	    var	n, s2, s3
	    var	n, s2, s3,
		jn = s.type == C.REST ? 0 : 8	// '0' or '-'

		if (s.dur >= C.BLEN)
			n = 3 
		else if (s.dur == C.BLEN / 2)
			n = 1
		else
			n = 2
		s.notes[0].dur =
		s.dur = s.dur_orig = C.BLEN / 4
		delete s.fmr
		while (--n >= 0) {
			s2 = {
				type: C.REST,
				v: s.v,
				p_v: s.p_v,
				st: s.st,
				dur: C.BLEN / 4,
				dur_orig: C.BLEN / 4,
				fmt: s.fmt,
				stem: 0,
				multi: 0,
				nhd: 0,
				notes: [{
					dur: s.dur,
					pit: s.notes[0].pit,
					jn: 8
					jn: jn
				}],
				xmx: 0,
				noplay: true,
				time: s.time + C.BLEN / 4,
				prev: s,
				next: s.next
			}
︙
157
158
159
160
161
162
163




























































164
165
166


167
168
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189

190
191
192
193
194
195

196
197

198
199
200
201
202
203
204
205


206
207
208

209
210
211

212
213
214

215
216
217
218
219

220
221
222

223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273








274
275
276
277
278
279

280
281
282
283
284
285
286
287
288

289
290
291
292
293
294
295



296
297
298


299
300
301
302
303
304
305
306
307
308
309
310
311
312
313

314
315
316
317
318
319
320
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267


268
269

270

271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290

291
292
293
294
295
296

297
298

299
300
301
302
303
304
305
306

307
308
309
310
311
312
313


314



315





316



317









































318
319
320
321
322
323
324
325


326
327
328
329
330
331
332
333
334
335
336
337
338

339
340
341
342
343
344
345
346
347

348
349
350
351
352
353


354
355
356
357


358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373

374
375
376
377
378
379
380
381







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+
-

-
+



















-
+





-
+

-
+







-
+
+



+

-
-
+
-
-
-
+
-
-
-
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-








-
-
+
+
+
+
+
+
+
+





-
+








-
+





-
-
+
+
+

-
-
+
+














-
+







				break
			    }
			}
			s = s2
		}
	} // slice()

	function set_note(s, sf) {
	    var	i, m, note, p, pit, a, nn,
		delta = abc2svg.jianpu.cgd2cde[sf + 7] - 2

		s.stem = -1
		s.stemless = true

		if (s.sls) {
			for (i = 0; i < s.sls.length; i++)
				s.sls[i].ty = C.SL_ABOVE
		}

		for (m = 0; m <= s.nhd; m++) {
			note = s.notes[m]

			// note head
			p = note.pit
			pit = p + delta
			note.jn = ((pit + 77) % 7) + 1	// note number

			// set a fixed offset to the note for the decorations
			note.pit = 25			// "e"

			note.jo = (pit / 7) | 0	// octave number

			// accidentals
			a = note.acc
			if (a) {
				nn = abc2svg.jianpu.cde2fcg[(p + 5 + 16 * 7) % 7] - sf
				if (a != 3)
					nn += a * 7
				nn = ((((nn + 1 + 21) / 7) | 0) + 2 - 3 + 32 * 5) % 5
				note.acc = abc2svg.jianpu.acc2[nn]
			}

			// set the slurs and ties up
			if (note.sls) {
				for (i = 0; i < note.sls.length; i++)
					note.sls[i].ty = C.SL_ABOVE
			}
			if (note.tie_ty)
				note.tie_ty = C.SL_ABOVE
		}

		// change the long notes
		if (s.dur >= C.BLEN / 2
		 && !s.invis)
			slice(s)

		// replace the staccato dot
		if (s.a_dd) {
			for (i = 0; i < s.a_dd.length; i++) {
				if (s.a_dd[i].glyph == "stc") {
					abc.deco_put("gstc", s)
					s.a_dd[i] = s.a_dd.pop()
				}
			}
		}
	} // set_note()

	function set_sym(p_v) {
	    var s, s2, note, pit, nn, p, a, m, i,
		sf = p_v.key.k_sf,
	    var s, g,
		sf = p_v.key.k_sf
		delta = abc2svg.jianpu.cgd2cde[sf + 7] - 2

		p_v.key.k_a_acc = []	// no accidental
		delete p_v.key.k_a_acc		// no accidental

		// no (visible) clef
		s = p_v.clef
		s.invis = true
		s.clef_type = 't'
		s.clef_line = 2

		// scan the voice
		for (s = p_v.sym; s; s = s.next) {
			s.st = p_v.st
			switch (s.type) {
			case C.CLEF:
				s.invis = true
				s.clef_type = 't'
				s.clef_line = 2
//				continue
			default:
				continue
			case C.KEY:
				delta = abc2svg.jianpu.cgd2cde[s.k_sf + 7] - 2
				sf = s.k_sf
				s.a_gch = [{
					type: '@',
					font: abc.get_font("annotation"),
					wh: [10, 10],
					x: -5,
					y: 30,
					y: 26,
					text: (s.k_mode + 1) + "=" +
						(abc2svg.jianpu.k_tb[s.k_sf + 7 +
						(abc2svg.jianpu.k_tb[sf + 7 +
							abc2svg.jianpu.cde2fcg[s.k_mode]])
				}]
				continue
			case C.REST:
				if (s.notes[0].jn)
					continue
				s.notes[0].jn = 0
				if (s.dur >= C.BLEN / 2)
				if (s.dur >= C.BLEN / 2
				 && !s.invis)
					slice(s)
				continue
			case C.NOTE:			// change the notes
				set_note(s, sf)
				break
			}

			case C.GRACE:
			s.stem = -1
			s.stemless = true

				for (g = s.extra; g; g = g.next)
			if (s.sls) {
				for (i = 0; i < s.sls.length; i++)
					s.sls[i].ty = C.SL_ABOVE
			}

					set_note(g, sf)
			for (m = 0; m <= s.nhd; m++) {
				note = s.notes[m]

				break
				// note head
				p = note.pit
				pit = p + delta
				note.jn = ((pit + 77) % 7) + 1	// note number

				// set a fixed offset to the note for the decorations
				note.pit = 25			// "e"

				note.jo = (pit / 7) | 0	// octave number

				// accidentals
				a = note.acc
				if (a) {
					nn = abc2svg.jianpu.cde2fcg[(p + 5 + 16 * 7) % 7] - sf
					if (a != 3)
						nn += a * 7
					nn = ((((nn + 1 + 21) / 7) | 0) + 2 - 3 + 32 * 5) % 5
					note.acc = abc2svg.jianpu.acc2[nn]
				}

				// set the slurs and ties up
				if (note.sls) {
					for (i = 0; i < note.sls.length; i++)
						note.sls[i].ty = C.SL_ABOVE
				}
				if (note.tie_ty)
					note.tie_ty = C.SL_ABOVE
			}

			// change the long notes
			if (s.dur >= C.BLEN / 2)
				slice(s)

			// replace the staccato dot
			if (s.a_dd) {
				for (i = 0; i < s.a_dd.length; i++) {
					if (s.a_dd[i].glyph == "stc") {
						abc.deco_put("gstc", s)
						s.a_dd[i] = s.a_dd.pop()
					}
				}
			}
		}
	} // set_sym()

	// -- output_music --

	set_head()

	for (v = 0; v < voice_tb.length; v++)
		set_sym(voice_tb[v])
	for (v = 0; v < voice_tb.length; v++) {
		p_v = voice_tb[v]
		if (p_v.jianpu) {
			set_sym(p_v)
			if (v > 0 && voice_tb[v - 1].st == p_v.st)
				ov_def(v)
		}
	}

	of()
  }, // output_music()

  draw_symbols: function(of, p_voice) {
    var	i, m, nl, note, s, s2, x, y,
    var	s, s2, nl, y,
	C = abc2svg.C,
	abc = this,
	dot = "\ue1e7",
	staff_tb = abc.get_staff_tb(),
	out_svg = abc.out_svg,
	out_sxsy = abc.out_sxsy,
	xypath = abc.xypath

	if (!abc.cfmt().jianpu) {
	if (!p_voice.jianpu) {
		of(p_voice)
		return
	}

	// draw the duration lines under the notes
	function draw_dur(s1, y, s2, n, nl) {
	    var s, s3
	function draw_dur(s1, x, y, s2, n, nl) {
	    var s, s3,
		sc = s1.grace ? .5 : 1

		xypath(s1.x - 3, y + 5)
		out_svg('h' + (s2.x - s1.x + 8).toFixed(1) + '"/>\n')	// "
		xypath(x - 3, y + 5)
		out_svg('h' + ((s2.x - s1.x) / sc + 8).toFixed(1) + '"/>\n')	// "
		y -= 2.5
		while (++n <= nl) {
			s = s1
			while (1) {
				if (s.nflags && s.nflags >= n) {
					s3 = s
					while (s != s2) {
						if (s.next.beam_br1
						 || (s.next.beam_br2 && n > 2)
						 || (s.next.nflags
						  && s.next.nflags < n))
							break
						s = s.next
					}
					draw_dur(s3, y, s, n, nl)
					draw_dur(s3, s3.x, y, s, n, nl)
				}
				if (s == s2)
					break
				s = s.next
			}
		}
	} // draw_dur()
︙
356
357
358
359
360
361
362




363



364





















365
366
367
368
369
370
371
372
373
374
375
376
377
378









379
380
381
382
383
384
385
386
387
388
389
390
391


















392
393
394
395
396
397
398




399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416

417
418
419
420
421
422
423
424
425
426

427
428
429
430
431
432


433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450

451
452
453
454





















455
456
457
458

459

460
461
462
463
464
465
466
467
468
469

470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487

488
489
490
491
492
493
494
417
418
419
420
421
422
423
424
425
426
427

428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461





462
463
464
465
466
467
468
469
470













471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488







489
490
491
492


493
494
495
496
497
498
499
500








501

502
503
504
505
506
507
508
509

510

511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566

567
568
569
570
571
572
573
574
575
576

577
578
579
580
581
582
583
584
585
586
587
588
589
590

591
592
593
594
595
596
597
598
599
600
601
602







+
+
+
+
-
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
-
-








-
-
-
-
-
-
-
-
+
-








-
+
-





+
+


















+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




+
-
+









-
+













-




+







					out_mus(x - 1, ym, dot)
				}
			}
			y += 20
		}
	} // draw_hd()

	function draw_note(s) {
	    var	sc = 1,
		x = s.x,
		y = staff_tb[s.st].y
	// -- draw_symbols --

		if (s.dy)
			y += s.dy			// voice overlay

		if (s.grace) {
			out_svg('<g transform="translate(')
			out_sxsy(x, ',', y + 15)	// (font height)
			out_svg(') scale(.5)">\n')
			abc.stv_g().g++			// in container
			x = 0
			y = 0
			sc = .5
		}

		draw_hd(s, x, y)

		if (s.nflags >= 0 && s.dots)
			out_mus(x + 8 * sc, y + 13 * sc, dot)
		if (s.grace) {
			out_svg('</g>\n')
			abc.stv_g().g--
		}
	} // draw_note()

	// -- draw_symbols --
	for (s = p_voice.sym; s; s = s.next) {
		if (s.invis)
			continue
		switch (s.type) {
		case C.METER:
			abc.draw_meter(s)
			break
		case C.NOTE:
		case C.REST:
			x = s.x
			y = staff_tb[s.st].y
			draw_hd(s, x, y)

			if (s.nflags >= 0 && s.dots)
			draw_note(s)
			break
		case C.GRACE:
			for (g = s.extra; g; g = g.next)
				draw_note(g)
			break
		}
	}

				out_mus(x + 8, y + 13, dot)
			if (s.nflags > 0) {
				if (s.time == p_voice.sym.time)
					s.beam_st = 1	// beam continuation
//fixme: ko with rests because no beam_st /_end
				if (s.beam_st || s.type == C.REST) {
					nl = s.nflags
					s2 = s
					while (1) {
						if (s2.nflags && s2.nflags > nl)
							nl = s2.nflags
						if (s2.beam_end)
							break
	// draw the (pseudo) beams
	for (s = p_voice.sym; s; s = s.next) {
		if (s.invis)
			continue
		switch (s.type) {
		case C.NOTE:
		case C.REST:
			nl = s.nflags
			if (nl <= 0)
				continue
			y = staff_tb[s.st].y
			s2 = s
			while (s.next && s.next.nflags > 0) {
				s = s.next
				if (s.nflags > nl)
					nl = s.nflags
				if (s.beam_end)
					break
						if (!s2.next
						 || !s2.next.nflags
						 || s2.next.nflags <= 0)
							break
						s2 = s2.next
					}
					draw_dur(s, y, s2, 1, nl)
			}
			if (s.dy)
				y += s.dy
			draw_dur(s2, s2.x, y, s, 1, nl)
				}
			}
			break
		}
	}
  }, // draw_symbols()

// set some parameters
    set_fmt: function(of, cmd, param) {
	if (cmd == "jianpu") {
	    var	cfmt = this.cfmt()

		if (!this.get_bool(param))
			return
		cfmt.jianpu = true
		cfmt.staffsep = 20
		cfmt.sysstaffsep = 14
		this.set_v_param("stafflines", "...")
		this.set_v_param("jianpu", param)
		cfmt.tuplets = [0, 1, 0, 1]	// [auto, slur, number, above]
		return
	}
	of(cmd, param)
    }, // set_fmt()

// adjust some values
    set_pitch: function(of, last_s) {
	of(last_s)
	if (!last_s
	if (!last_s)
	 || !this.cfmt().jianpu)
		return			// first time

    var	C = abc2svg.C
	
	for (var s = this.get_tsfirst(); s; s = s.ts_next) {
		if (!s.p_v.jianpu)
			continue
		switch (s.type) {

		// draw the key signature only in the first voice
		// and not at start of the staff
		case C.KEY:
			if (s.prev.type == C.CLEF
			 || s.v != 0)
				s.a_gch = null
			break

		// adjust the vertical spacing above the note heads
		case C.NOTE:
			s.ymx = 20 * s.nhd + 22
			if (s.notes[s.nhd].jo > 2) {
				s.ymx += 3
				if (s.notes[s.nhd].jo > 3)
					s.ymx += 2
			}
			s.ymn = 0		// bottom of line
			break
		}
	}
    }, // set_pitch()

    set_vp: function(of, a) {
    var	i,
	p_v = this.get_curvoice()

	for (i = 0; i < a.length; i++) {
		if (a[i] == "jianpu=") {
			p_v.jianpu = this.get_bool(a[++i])
			if (p_v.jianpu)
				this.set_vp([
					"staffsep=", "20",
					"sysstaffsep=", "14",
					"stafflines=", "...",
					"tuplets=", "0 1 0 1"
						// [auto, slur, number, above]
				])
			break
		}
	}
	of(a)
    }, // set_vp()

// set the width of some symbols
    set_width: function(of, s) {
	of(s)
	if (!s.p_v			// (if voice_tb[v].clef/key/meter)
	if (!this.cfmt().jianpu)
	 || !s.p_v.jianpu)
		return

    var	w, m, note,
	C = abc2svg.C

	switch (s.type) {
	case C.CLEF:
	case C.KEY:
//	case C.METER:
		s.wl = s.wr = 0
		s.wl = s.wr = .1		// (must not be null)
		break
	case C.NOTE:
		for (m = 0; m <= s.nhd; m++) {
			note = s.notes[m]
			if (note.acc && s.wl < 14)	// room for the accidental
				s.wl = 14
		}
		break
	}
    }, // set_width()

    set_hooks: function(abc) {
	abc.calculate_beam = abc2svg.jianpu.calc_beam.bind(abc, abc.calculate_beam)
	abc.do_pscom = abc2svg.jianpu.do_pscom.bind(abc, abc.do_pscom)
	abc.draw_symbols = abc2svg.jianpu.draw_symbols.bind(abc, abc.draw_symbols)
	abc.output_music = abc2svg.jianpu.output_music.bind(abc, abc.output_music)
	abc.set_format = abc2svg.jianpu.set_fmt.bind(abc, abc.set_format)
	abc.set_pitch = abc2svg.jianpu.set_pitch.bind(abc, abc.set_pitch)
	abc.set_vp = abc2svg.jianpu.set_vp.bind(abc, abc.set_vp)
	abc.set_width = abc2svg.jianpu.set_width.bind(abc, abc.set_width)

	// big staccato dot
	abc.get_glyphs().gstc = '<circle id="gstc" cx="0" cy="-3" r="2"/>'
	abc.get_decos().gstc = "0 gstc 5 1 1"

	abc.add_style("\n.fj{font:15px sans-serif}")
︙
Changes to modules/mdnn.js.
1
2
3
4

5
6
7
8
9
10
11
1
2
3

4
5
6
7
8
9
10
11



-
+







// mdnn.js - module to output Modernised Diatonic Numerical Notation
// (https://medium.com/@info_70544/the-case-for-numerical-music-notation-part-1-introduction-and-history-5f1543ca8a95)
//
// Copyright (C) 2020-2021 Jean-Francois Moine
// Copyright (C) 2020-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
116
117
118
119
120
121
122
123

124
125
126
127
128
129
130
131
132
133
134
135

136
137
138
139
140
141
142
116
117
118
119
120
121
122

123
124
125
126
127
128
129
130
131
132
133
134

135
136
137
138
139
140
141
142







-
+











-
+







	prev_oct = -10

	if (!cfmt.mdnn) {
		of()
		return
	}

	voice_tb[0].key.k_a_acc = []	// no accidental
	delete voice_tb[0].key.k_a_acc	// no accidental
	voice_tb[0].clef.invis = true	// no (visible) clef
	cur_sy.staves[0].stafflines = "..." // empty staff

	// scan the first voice of the tune
	for (s = voice_tb[0].sym; s; s = s.next) {
		switch (s.type) {
		case C.CLEF:
			s.invis = true
			continue
		case C.KEY:
//fixme:nsk
//			s.k_a_acc = []		// don't display the accidentals
//			delete s.k_a_acc	// don't display the accidentals
			sf = s.k_sf
			delta = abc2svg.mdnn.cgd2cde[sf + 7] - 2
			nn = sf - s.k_old_sf	// delta accidentals
//fixme:nsk
//			if (!nn) {
//				s.k_old_sf = s.k_sf = 0 // don't display the accidentals
//				continue
︙
265
266
267
268
269
270
271
272

273
274
275
276
277
278
279
265
266
267
268
269
270
271

272
273
274
275
276
277
278
279







-
+







		return

    var	C = abc2svg.C,
	s = this.get_tsfirst()

	if (s && s.next && s.next.type == C.KEY)
//fixme:nsk
		s.next.k_a_acc = []
		delete s.next.k_a_acc
//		s.next.a_gch = null
    }, // set_pitch()

    set_hooks: function(abc) {
	abc.output_music = abc2svg.mdnn.output_music.bind(abc, abc.output_music)
	abc.set_format = abc2svg.mdnn.set_fmt.bind(abc, abc.set_format)
	abc.set_pitch = abc2svg.mdnn.set_pitch.bind(abc, abc.set_pitch)
︙
Changes to modules/nns.js.
︙
109
110
111
112
113
114
115
116

117
118
119
120
121
122
123
124
125
126
127

128
129
130
131
132
133
134
109
110
111
112
113
114
115

116
117
118
119
120
121
122
123
124
125
126

127
128
129
130
131
132
133
134







-
+










-
+







	} // get_l()

	function build_cell(c) {
	    var	i, j, k,
		u = '',
		t = ''

		row += '   '
		row += '   '
		if (c.length == 1) {
			row += c[0]	// one chord in the measure
			return
		}
		i = 0
		while (i < 4) {
			t += c[i]
			k = get_l(c[i])
			j = k
			while (--j >= 0)
				u += ' '	// &nbsp;
				u += ' '
			if (i < 3 && !c[i + 1]) {
				while (i < 3 && !c[i + 1])
					i++
				i++
				if (i < 4 && c[i])
					t += ' '
			} else {
︙
Changes to modules/page.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// page.js - module to generate pages
//
// Copyright (C) 2018-2021 Jean-Francois Moine
// Copyright (C) 2018-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

44

45
46


47
48
49
50
51
52
53

54
55
56
57
58
59
60
61
62
20
21
22
23
24
25
26













27
28
29

30
31
32


33
34
35
36
37
38
39
40

41
42

43
44
45
46
47
48
49







-
-
-
-
-
-
-
-
-
-
-
-
-



-
+

+
-
-
+
+






-
+

-







// This module is loaded when "%%pageheight" appears in a ABC source.
//
// Parameters
//	%%pageheight <unit>

abc2svg.page = {

    // output a new block
    img_out: function(page, p) {
    var	cur_img_out = user.img_out;

	page.user_out(p)
	
	// if user.img_out has been changed
	if (user.img_out != cur_img_out) {
		page.user_out = user.img_out	// save the new function
		user.img_out = cur_img_out	// and restore ours
	}
    },

    // function called at end of generation
    abc_end: function(of) {
    var page = this.page
	if (page && page.in_page) {
	if (page && page.in_page)
		abc2svg.page.close_page(page)
	if (abc2svg.page.user_out) {
		if (user.img_out == page.img_out_sav)
			user.img_out = page.user_out
		user.img_out = abc2svg.page.user_out
		abc2svg.page.user_out = null
	}
	of()
    }, // abc_end()

    // generate a header or a footer in page.hf and return its height
    gen_hf: function(page, ty) {
    var	a, i, j, k, x, y, y0, s,
    var	a, i, j, k, x, y, y0, s, str,
	font = page.abc.get_font(ty),
	str = page[ty],
	cfmt = page.abc.cfmt(),
	fh = font.size * 1.1,
	pos = [ '">',
		'" text-anchor="middle">',
		'" text-anchor="end">' ]

	// replace <>& by XML character references
︙
185
186
187
188
189
190
191





192
193
194
195
196
197
198
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190







+
+
+
+
+







	function font_class(font) {
		if (font.class)
			return 'f' + font.fid + cfmt.fullsvg + ' ' + font.class
		return 'f' + font.fid + cfmt.fullsvg
	}

	// gen_hf

	if (!(page.pn & 1))
		str = page[ty + '2'] || page[ty]
	else
		str = page[ty]

	if (str[0] == '-') {		// not on 1st page
		if (page.pn == 1)
			return 0
		str = str.slice(1)
	}

︙
247
248
249
250
251
252
253
254

255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272

273
274
275
276
277
278
279
280
281
282
283
284

285
286
287
288
289
290
291
239
240
241
242
243
244
245

246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263

264
265
266
267
268
269
270
271
272
273
274
275

276
277
278
279
280
281
282
283







-
+

















-
+











-
+







	if (page.first)
		page.first = false
	else
		sty += ";page-break-before:always"
	if (page.gutter)
		sty += ";margin-left:" +
			((page.pn & 1) ? page.gutter : -page.gutter).toFixed(1) + "px"
	abc2svg.page.img_out(page, sty + '">')
	abc2svg.page.user_out(sty + '">')
	page.in_page = true

	ht += page.topmargin
	page.hmax = cfmt.pageheight - page.botmargin - ht

	// define the header/footer
	page.hf = ''
	if (page.header) {
		l = abc.get_font_style().length
		h = abc2svg.page.gen_hf(page, "header")
		sty = abc.get_font_style().slice(l)		// new style(s)
		if (cfmt.fullsvg || sty != page.hsty) {
			page.hsty = sty
			sty = '<style>' + sty + '\n</style>\n'
		} else {
			sty = ''
		}
		abc2svg.page.img_out(page,
		abc2svg.page.user_out(
			'<svg xmlns="http://www.w3.org/2000/svg" version="1.1"\n\
	xmlns:xlink="http://www.w3.org/1999/xlink"\n\
	width="' + cfmt.pagewidth.toFixed(0) +
			'px" height="' + (ht + h).toFixed(0) +
			'px">\n' + sty +
			'<g transform="translate(0,' +
				page.topmargin.toFixed(1) + ')">' +
				page.hf + '</g>\n</svg>')
		page.hmax -= h;
		page.hf = ''
	} else {
		abc2svg.page.img_out(page,
		abc2svg.page.user_out(
			'<svg xmlns="http://www.w3.org/2000/svg" version="1.1"\n\
	xmlns:xlink="http://www.w3.org/1999/xlink"\n\
	width="' + cfmt.pagewidth.toFixed(0) +
			'px" height="' + ht.toFixed(0) +
			'px">\n</svg>')
	}
	if (page.footer) {
︙
307
308
309
310
311
312
313
314

315
316
317
318
319
320
321
322
323
324

325
326
327
328
329
330
331
332
333
334
335
336

337
338
339
340
341
342
343
299
300
301
302
303
304
305

306
307
308
309
310
311
312
313
314
315

316
317
318
319
320
321
322
323
324
325
326
327

328
329
330
331
332
333
334
335







-
+









-
+











-
+







    close_page: function(page) {
    var	h,
	cfmt = page.abc.cfmt()

	page.in_page = false
	if (page.footer) {
		h = page.hmax + page.fh - page.h
		abc2svg.page.img_out(page,
		abc2svg.page.user_out(
			'<svg xmlns="http://www.w3.org/2000/svg" version="1.1"\n\
	xmlns:xlink="http://www.w3.org/1999/xlink"\n\
	width="' + cfmt.pagewidth.toFixed(0) +
			'px" height="' + h.toFixed(0) +
			'px">\n' + page.ffsty +
			'<g transform="translate(0,' +
				(h - page.fh).toFixed(1) + ')">' +
			page.hf + '</g>\n</svg>')
	}
	abc2svg.page.img_out(page, '</div>')
	abc2svg.page.user_out('</div>')
	page.h = 0
    }, // close_page()

    // handle the output flow of the abc2svg generator
    img_in: function(p) {
    var h, ht, nh,
	page = this.page

	// copy a block
	function blkcpy(page) {
		while (page.blk.length)
			abc2svg.page.img_out(page, page.blk.shift())
			abc2svg.page.user_out(page.blk.shift())
		page.blk = null			// direct output
	} // blkcpy()

	// img_in()
	switch (p.slice(0, 4)) {
	case "<div":				// block of new tune
		if (p.indexOf('newpage') > 0
︙
376
377
378
379
380
381
382
383

384
385
386
387
388
389
390
368
369
370
371
372
373
374

375
376
377
378
379
380
381
382







-
+







				break		// error
		}

		// if no overflow yet, keep the block
		if (page.blk)
			page.blk.push(p)
		else
			abc2svg.page.img_out(page, p)
			abc2svg.page.user_out(p)
		page.h += h
		break
	case "</di":				// end of block
		if (page.blk)
			blkcpy(page)
		break
//	default:
︙
401
402
403
404
405
406
407


408


409
410
411

412
413
414
415
416
417
418
419
420

421
422
423
424
425
426
427
428
429
430
431
432








433
434
435
436
437
438
439
393
394
395
396
397
398
399
400
401
402
403
404
405
406

407
408
409
410
411
412
413
414
415

416

417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442







+
+

+
+


-
+








-
+
-











+
+
+
+
+
+
+
+








	if (cmd == "pageheight") {
		v = this.get_unit(parm)
		if (isNaN(v)) {
			this.syntax(1, this.errs.bad_val, '%%' + cmd)
			return
		}
		if (!user.img_out || !abc2svg.abc_end)
			v = 0
		cfmt.pageheight = v
		if (!v)
			return

		// if first definition, install the hook
		if (!page && user.img_out && abc2svg.abc_end) {
		if (!page || !abc2svg.page.user_out) {
			this.page = page = {
				abc: this,
				topmargin: 38,	// 1cm
				botmargin: 38,	// 1cm
//				gutter: 0,
				h: 0,		// current page height
				pn: 0,		// page number
				pna: 0,		// absolute page number
				first: true,	// no skip to next page
				first: true	// no skip to next page
				user_out: user.img_out
			}

			// don't let the backend handle the header/footer
			if (cfmt.header) {
				page.header = cfmt.header;
				cfmt.header = null
			}
			if (cfmt.footer) {
				page.footer = cfmt.footer;
				cfmt.footer = null
			}
			if (cfmt.header2) {
				page.header2 = cfmt.header2
				cfmt.header2 = null
			}
			if (cfmt.footer2) {
				page.footer2 = cfmt.footer2
				cfmt.footer2 = null
			}

			// get the previously defined page parameters
			if (cfmt.botmargin != undefined) {
				v = this.get_unit(cfmt.botmargin)
				if (!isNaN(v))
					page.botmargin = v
			}
︙
449
450
451
452
453
454
455


456
457
458
459
460
461
462
463
464
465
466


467
468
469
470
471
472
473
452
453
454
455
456
457
458
459
460
461

462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479







+
+

-









+
+







			}
			if (cfmt.oneperpage)
				page.oneperpage = this.get_bool(cfmt.oneperpage)
			if (!cfmt.dateformat)
				cfmt.dateformat = "%b %e, %Y %H:%M"

			// set the hooks
			if (!abc2svg.page.user_out)
				abc2svg.page.user_out = user.img_out
			user.img_out = abc2svg.page.img_in.bind(this);
			page.img_out_sav = user.img_out;
			abc2svg.abc_end = abc2svg.page.abc_end.bind(this,
								abc2svg.abc_end)
		}
		return
	}
	if (page) {
		switch (cmd) {
		case "header":
		case "footer":
		case "header2":
		case "footer2":
			page[cmd] = parm
			return
		case "newpage":
			if (!parm)
				break
			v = Number(parm)
			if (isNaN(v)) {
︙
Changes to modules/pedline.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// pedline.js - module to draw pedal lines instead of 'Ped .. *'
//
// Copyright (C) 2020-2021 Jean-Francois Moine
// Copyright (C) 2020-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
41
42
43
44
45
46
47
48
49

50
51
52
53
54
55
56
41
42
43
44
45
46
47


48
49
50
51
52
53
54
55







-
-
+







//	\ de.prev
// \ de.prev.start
// |_____/\____|
				de.defl.nost = true
				de.prev.defl.noen = true
				de.x = de.prev.s.x - 5
				de.val = de.s.x - de.x - 5
				if (!de.prev.prev)
					de.prev.val = de.x - de.prev.x
				de.prev.val = de.x - de.prev.x
			} else {
				de.x -= 3
				de.val += 10
			}
		}
	}
	of()
︙
Changes to modules/perc.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// perc.js - module to handle %%percmap
//
// Copyright (C) 2018-2021 Jean-Francois Moine
// Copyright (C) 2018-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
205
206
207
208
209
210
211
212

213
214
215
216
217
218
219
220
221
222
223



224
225
226
227
228
229
230
205
206
207
208
209
210
211

212
213
214
215
216
217
218
219
220



221
222
223
224
225
226
227
228
229
230







-
+








-
-
-
+
+
+







		" print=" + p +
		(a[3] ? (" heads=" + a[3]) : ''))
	this.set_v_param("perc", "MIDIdrum")
    }, // do_perc()

    // set the MIDI parameters in the current voice
    set_perc: function(a) {
    var	i, item,
    var	i, item, s,
	curvoice = this.get_curvoice()

	for (i = 0; i < a.length; i++) {
		switch (a[i]) {
		case "perc=":				// %%percmap
			if (!curvoice.map)
				curvoice.map = {}
			curvoice.map = a[i + 1];
			if (!curvoice.midictl)
				curvoice.midictl = []
			curvoice.midictl[0] = 1		// bank 128
			s = this.new_block("midiprog")
			s.play = s.invis = 1 //true
			curvoice.chn = s.chn = 9	// channel 10
			break
		}
	}
    }, // set_perc()

    do_pscom: function(of, text) {
	if (text.slice(0, 8) == "percmap ")
︙
Changes to modules/psvg.js.
︙
469
470
471
472
473
474
475
476

477
478
479
480
481
482
483
484
485
486
487
488
469
470
471
472
473
474
475

476
477
478
479
480

481
482
483
484
485
486
487







-
+




-







	} else if (g != 2) {
		if (!g)
			defg1()
		svgbuf += '<text x="' + (x + gcur.xoffs).toFixed(1) + '" y="' +
				(gcur.yoffs - y).toFixed(1) + '">';
		g = 2
	}
	svgbuf += s.replace(/<|>|&|  /g, function(c){
	svgbuf += s.replace(/<|>|&/g, function(c){
			switch (c) {
			case '<': return "&lt;"
			case '>': return "&gt;"
			case '&': return "&amp;"
			case '  ': return '  '		// space + nbspace
			}
		})
	if (span)
		svgbuf += "</tspan>";
	gcur.cx = x + strw(s)
    }

︙
Changes to modules/sth.js.
︙
30
31
32
33
34
35
36
37

38
39

40
41
42
43
44
45
46
30
31
32
33
34
35
36

37
38

39
40
41
42
43
44
45
46







-
+

-
+







// function called after beam calculation
    recal_beam: function(bm, s) {
    var staff_tb = this.get_staff_tb(),
	y = staff_tb[s.st].y,
	s2 = bm.s2,
	y2 = staff_tb[s2.st].y

	if (s.sth)
	if (s.sth != undefined)
		s.ys = s.sth
	if (s2.sth)
	if (s2.sth != undefined)
		s2.ys = s2.sth;
	bm.a = (s.ys + y - s2.ys - y2) / (s.xs - s2.xs);
	bm.b = s.ys - s.xs * bm.a + y
	while (1) {
		s.ys = bm.a * s.xs + bm.b - y
		if (s.stem > 0)
			s.ymx = s.ys + 2.5
︙
Changes to modules/strtab.js.
︙
22
23
24
25
26
27
28





29
30
31
32
33
34
35
36
37

38
39


































































































40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82



83
84
85
86




87
88

89
90
91

92
93
94
95

96
97
















98
99
100
101
102
103
104
105
106
107





108
109
110
111
112
113
114




115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

136
137
138
139
140
141
142
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172




173
174
175
176
177
178

179

180
181
182
183



184
185
186
187


188



189




190


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236














237
238

239
240
241
242
243
244
245
246







+
+
+
+
+








-
+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+















-
+














-
-
-
-






-

-
+
+
+

-
-
-
+
+
+
+
-
-
+
-
-
-
+
-
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+










+
+
+
+
+







+
+
+
+




-
-
-
-
-
-
-
-
-
-
-
-
-
-


-
+







// Syntax:
//	%%strtab <string list> [diafret]
// <string list> is the list of the strings as ABC notes
// diafret indicates the instrument has diatonic frets
// The fret may be forced by a decoration format
//	"!" digit "s!"
// where 'digit' is the string range in the string list (last string is '1')
//
// The command %%minfret permits to force upper frets.
// Syntax:
//	%%minfret [ <string number> : <fret number> ]*
// Each command replaces the previous value.
//
// This module accepts Willem Vree's tablature syntax:
//	https://wim.vree.org/svgParse/abc2xml.html#tab

abc2svg.strtab = {

    // draw the tablature
    draw_symbols: function(of, p_v) {
    var	s, m, not, stb, x, y,
    var	s, m, not, stb, x, y, g,
	C = abc2svg.C,
	abc = this

	// draw the note heads
	function draw_heads(stb, s) {
	    var	m, not, x, y

		for (m = 0; m <= s.nhd; m++) {
			not = s.notes[m]
			if (not.nb < 0)
				continue
			x = s.x - 3
			if (not.nb >= 10)
				x -= 3
			y = 3 * (not.pit - 18)
			abc.out_svg('<text class="bg' + abc.bgn +
				'" x="')
			abc.out_sxsy(x, '" y="', stb + y - 2.5)
			abc.out_svg('">' + not.nb + '</text>\n')
		}
	} // draw_heads()

	// draw the stems, flags and beams
	function draw_stems(stb, s) {
		if (!s.tabst)
			return			// don't draw any stem
	    var	s1, s2, nfl, l,
		y = stb + 3 * (s.notes[0].pit - 19) // -18) - 3
				* s.p_v.staffscale,
		h = (11 + 3 * (s.notes[0].pit - 18)) * s.p_v.staffscale

		abc.out_svg('<path class="sW" d="M')
		abc.out_sxsy(s.x, ' ', y)
		abc.out_svg('v' + h.toFixed(1) +'"/>\n')

		// draw the flag(s)
		if (s.nflags <= 0
		 || !s.beam_end)		// inside beam
			return
		y -= h
		if (s.beam_st) {		// no beam
			abc.out_svg('<text class="f'
				+ abc.get_font('music').fid
				+ '" transform="translate(')
			abc.out_sxsy(s.x, ',', y)
			abc.out_svg(') scale('
				+ (s.grace ? '.5,.5' : '.7,.7') + ')">'
				+ String.fromCharCode(0xe23f + 2 * s.nflags)
				+ '</text>\n')
			return
		}

		// draw the beam(s)
		s2 = s
		nfl = s.nflags
		while (1) {
			if (s.nflags > nfl)
				nfl = s.nflags
			if (s.beam_st)
				break
			s = s.prev
		}
		s1 = s
		l = (s2.x - s1.x).toFixed(1)
		abc.out_svg('<path d="M')
		abc.out_sxsy(s1.x, ' ', y)
		abc.out_svg('h' + l
				+ 'v-3h-' + l
				+ 'v3"/>\n')
		if (nfl == 1)
			return
//fixme: do more levels
		y += 5
		while (1) {
			while (s.nflags < 2) {
				s = s.next
				if (s == s2)
					break
			}
			if (s == s2)
				break
			s1 = s
			while (s.next.nflags >= 2) {
				s = s.next
				if (s == s2)
					break
			}
			l = (s.x - s1.x).toFixed(1)
			abc.out_svg('<path d="M')
			abc.out_sxsy(s1.x, ' ', y)
			abc.out_svg('h' + l
					+ 'v-3h-' + l
					+ 'v3"/>\n')
			if (s == s2)
				break
			s = s.next
			if (s == s2)
				break
		}
	} // draw_stems()

	if (!p_v.tab) {
		of(p_v)
		return
	}

	// define the 'bgx' filter if not done yet
	m = abc.cfmt().bgcolor || "white"
	if (abc.bgt != m) {
		if (!abc.bgn)
			abc.bgn = 1
		else
			abc.bgn++
		abc.bgt = m
		abc.defs_add('\
<filter x="-0.1" y="0.2" width="1.2" height=".8" id="bg' + abc.bgn + '">\n\
<filter x="-0.1" y="0.1" width="1.2" height=".8" id="bg' + abc.bgn + '">\n\
<feFlood flood-color="' + m + '"/>\n\
<feComposite in="SourceGraphic" operator="over"/>\n\
</filter>')
		abc.add_style('\n.bg' + abc.bgn + '{filter:url(#bg' + abc.bgn + ')}')
	}

	// adjust the symbol before generation
	for (s = p_v.sym; s; s = s.next) {
		switch (s.type) {
		case C.KEY:
		case C.METER:
		case C.REST:
			s.invis = true
			break
		case C.NOTE:
			if (!s.stemless)
				s.ys = -10
			break
		}
	}
	of(p_v)

	abc.glout()			// output the stems

	// draw the note heads
	stb = abc.get_staff_tb()[p_v.st].y
	abc.out_svg('<g class="bn">\n')

	// draw the stems and beams
	abc.set_sscale(-1)		// no scale
	for (s = p_v.sym; s; s = s.next) {
		if (s.type == C.NOTE) {
			for (m = 0; m <= s.nhd; m++) {
				not = s.notes[m]
		switch (s.type) {
		case C.GRACE:
			for (g = s.extra; g; g = g.next)
				draw_stems(stb, g)
				if (not.nb < 0)
					continue
			break
				x = s.x - 3
				if (not.nb >= 10)
					x -= 3
		case C.NOTE:
				y = 3 * (not.pit - 18)
				abc.out_svg('<text class="bg' + abc.bgn +
					'" x="')
				abc.out_sxsy(x, '" y="', stb + y - 2.5)
			draw_stems(stb, s)
				abc.out_svg('">' + not.nb + '</text>\n')
			}
			break
		}
	}

	// draw the note heads
	abc.set_scale(p_v.sym)		// (for draw_all_ties)
	abc.out_svg('<g class="bn">\n')
	for (s = p_v.sym; s; s = s.next) {
		switch (s.type) {
		case C.GRACE:
			for (g = s.extra; g; g = g.next)
				draw_heads(stb, g)
			break
		case C.NOTE:
			draw_heads(stb, s)
			break
		}
	}
	abc.out_svg('</g>\n')
    }, // draw_symbols()

    // set a format parameter
    set_fmt: function(of, cmd, parm) {
	if (cmd == "strtab") {
		if (!parm)
			return
	    var	p_v = this.get_curvoice()
		if (!p_v) {
			this.parse.tab = parm
			return
		}
		this.set_v_param("clef", "tab")
		if (parm.indexOf("diafret") >= 0) {
			this.set_v_param("diafret", true)
			parm = parm.replace(/\s*diafret\s*/, "")
		}
		this.set_v_param("strings", parm)
		return
	}
	if (cmd == "minfret") {
		this.set_v_param("minfret", parm)
		return
	}
	of(cmd, parm)
    }, // set_fmt()

    // adjust the horizontal offset of the stems
    set_width: function(of, s) {
    var	m, not,
	abc = this,
	C = abc2svg.C,
	o = s.stem < 0 ? 3.5 : -2.5

	of(s)
	if (s.p_v && s.p_v.tab && s.type == C.NOTE && !s.stemless) {
		for (m = 0; m <= s.nhd; m++)
			s.notes[m].shhd = o
	}
    }, // set_width()

    // change the notes when the global generation settings are done
    set_stems: function(of) {
    var	p_v, i, m, nt, n, bi, bn, strss,
    var	p_v, i, m, nt, n, bi, bn, strss, g,
	C = abc2svg.C,
	abc = this,
	s = abc.get_tsfirst(),		// first symbol
	strs = [],			// notes per staff - index = staff
	lstr = []			// lowest string per staff - index staff

	// set a string (pitch) and a fret number
︙
150
151
152
153
154
155
156

157
158


159
160
161
162
163
164
165
166
167










































































168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186


187
188
189











190
191
192
193
194
195
196
197
198
199
200
201

202
203

204
205
206

207
208

209
210
211

212
213
214
215
216



217
218

219
220
221
222

223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255

256
257

258
259
260
261
262
263
264
265


266
267
268
269
270
271
272
273
274
275
276
277
278

279
280
281
282
283
284
285
254
255
256
257
258
259
260
261


262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395

396



397


398



399





400
401
402


403




404

















405
















406


407








408
409



410
411
412
413
414
415
416
417
418

419
420
421
422
423
424
425
426







+
-
-
+
+









+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



















+
+



+
+
+
+
+
+
+
+
+
+
+












+

-
+
-
-
-
+
-
-
+
-
-
-
+
-
-
-
-
-
+
+
+
-
-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
-
-
-
-
-
-
-
-
+
+
-
-
-









-
+







			nt.pit = i * 2 + 18
		} else {
			nt.nb = -1
			nt.pit = 18
		}
		nt.acc = 0
		nt.invis = true
		if (!s.grace)
		strss[i] = s.time + s.dur
		if (s.nflags >= -1 && !s.stemless) {
			strss[i] = s.time + s.dur
		if (s.dur <= C.BLEN / 2 && !s.stemless) {
			if (!lstr[st])
				lstr[st] = [ 10 ]
			if (lstr[st][0] > i) {
				lstr[st][0] = i
				lstr[st][1] = s
			}
			s.stemless = true
		}
	} // set_pit()

	function set_notes(p_v, s) {
	    var	i, bi, bn, nt, m, n, ns

		s.stem = -1			// down stems

		// handle the fret numbers as chord decoration
		if (!s.nhd && s.a_dd) {
			i = s.a_dd.length
			while (--i >= 0) {
				bi = strnum(s.a_dd[i].name)
				if (bi >= 0) {
					nt = s.notes[0]
					set_pit(p_v, s, nt, bi)
					break
				}
			}
		}
		delete s.a_dd

		if (s.sls) {			// set the slurs above the staff
			for (i = 0; i < s.sls.length; i++) {
				s.sls[i].ty &= ~0x07
				s.sls[i].ty |= C.SL_ABOVE
			}
		}

	ls:	for (m = 0; m <= s.nhd; m++) {
			nt = s.notes[m]
			if (nt.sls) {			// if start of slur
				for (i = 0; i < nt.sls.length; i++) {
					nt.sls[i].ty &= ~0x07
					nt.sls[i].ty |= C.SL_ABOVE
				}
			}
			if (nt.nb != undefined)
				continue
			if (nt.a_dd) {
				i = nt.a_dd.length
				while (--i >= 0) {
					bi = strnum(nt.a_dd[i].name)
					if (bi >= 0) {
						set_pit(p_v, s, nt, bi)
						delete nt.a_dd
						continue ls
					}
				}
				delete nt.a_dd
			}

			// search the best string
			bn = 100
			bi = -1
			ns = i = p_v.tab.length
			while (--i >= 0) {
				if (strss[i] && strss[i] > s.time)
					continue
				n = (p_v.diafret ?
					nt.pit : nt.midi) -
						p_v.tab[i]
				if (n >= 0 && n < bn
				 && (!p_v.minfret
				  || !p_v.minfret[ns - i]
				  || n >= p_v.minfret[ns - i])) {
					bi = i
					bn = n
				}
			}
			set_pit(p_v, s, nt, bi)
		}

		s.y = 3 * (nt.pit - 18)
		s.ymn = 0		// don't get space below the tablature
	} // set_notes()

	// get the string number from the decoration
	// format is either !<n>s! or !<n>!
	function strnum(n) {
		n = n.match(/^([1-9])s?$/)
		return n ? p_v.tab.length - n[1] : -1
	} // strnum()

	// change the notes of the strings when a capo
	p_v = abc.get_voice_tb()
	for (n = 0; n < p_v.length; n++) {
		if (!p_v[n].tab)
			continue
		m = p_v[n].capo
		if (m) {
			for (i = 0; i < p_v[n].tab.length; i++)
				p_v[n].tab[i] += m
		}
	}

	of()				// do the normal work

	// loop on the notes of the voices with a tablature
	for ( ; s; s = s.ts_next) {

		// let a stem on the lowest string
		if (s.seqst || (s.ts_prev && s.ts_prev.type == C.GRACE)) {
			for (i = 0; i < lstr.length; i++) {
				if (lstr[i]) {
					lstr[i][1].tabst = 1
					lstr[i] = null
				}
			}
		}

		p_v = s.p_v
		if (!p_v.tab)
			continue
		strss = strs[s.st]
		if (!strss)
			strss = strs[s.st] = []
		switch (s.type) {
		case C.KEY:
		case C.REST:
		case C.TIME:
			s.invis = true
		default:
			delete s.a_dd
			continue
		case C.NOTE:
		case C.GRACE:
			break
		}

			if (p_v.pos.gst == C.SL_HIDDEN)
		s.stem = -1			// down stems

				s.sappo = 0
		// handle the fret numbers as chord decoration
		if (!s.nhd && s.a_dd) {
			i = s.a_dd.length
			for (g = s.extra; g; g = g.next) {
			while (--i >= 0) {
				bi = strnum(s.a_dd[i].name)
				if (bi >= 0) {
					nt = s.notes[0]
					set_pit(p_v, s, nt, bi)
				if (p_v.pos.gst == C.SL_HIDDEN)
					g.stemless = true
				set_notes(p_v, g)
					break
				}
			}
			}
			delete s.a_dd
		}

			break
	ls:	for (m = 0; m <= s.nhd; m++) {
			nt = s.notes[m]
			if (nt.nb != undefined)
				continue
			if (nt.a_dd) {
				i = nt.a_dd.length
				while (--i >= 0) {
					bi = strnum(nt.a_dd[i].name)
					if (bi >= 0) {
						set_pit(p_v, s, nt, bi)
						delete nt.a_dd
						continue ls
					}
				}
				delete nt.a_dd
			}

		case C.NOTE:
			// search the best string
			bn = 100
			bi = -1
			i = p_v.tab.length
			while (--i >= 0) {
				if (strss[i] && strss[i] > s.time)
					continue
				n = (p_v.diafret ?
					nt.pit : nt.midi) -
						p_v.tab[i]
				if (n >= 0 && n < bn) {
					bi = i
					bn = n
				}
			}
			set_pit(p_v, s, nt, bi)
			set_notes(p_v, s)
		}

			break
		// put the stems on the lowest strings
		if (!s.ts_next || s.ts_next.time != s.time) {
			for (i = 0; i < lstr.length; i++) {
				if (lstr[i]) {
					delete lstr[i][1].stemless
					lstr[i] = null
				}
			}
		}
	}
		}
	}
	of()
    }, // set_stems()

    // get the parameters of the current voice
    set_vp: function(of, a) {
    var	i, e, g, tab, strs, ok,
	p_v = this.get_curvoice()

	// convert a list of ABC notes into a list of MIDI pitches
	function abc2tab(p) {
	    var	i, c,
	    var	i, c, a,
		t = []

		if (p_v.diafret) {
		    for (i = 0; i < p.length; i++) {
			c = p[i]
			c = "CDEFGABcdefgab".indexOf(c)
			if (c < 0)
︙
297
298
299
300
301
302
303










304
305
306
307

308
309
310
311
312
313
314
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457

458
459
460
461
462
463
464
465







+
+
+
+
+
+
+
+
+
+



-
+







				}
			}
			t.push(c)
		    }
		} else {
		    for (i = 0; i < p.length; i++) {
			c = p[i]
			switch (c) {
			case '^':
			case '_':
				a = c == '^' ? 1 : -1
				c = p[++i]
				break
			default:
				a = 0
				break
			}
			c = "CCDDEFFGGAABccddeffggaab".indexOf(c)
			if (c < 0)
				return // null
			c += 60
			c += 60 + a
			while (1) {
				if (p[i + 1] == "'") {
					c += 12
					i++
				} else if (p[i + 1] == ",") {
					c -= 12
					i++
︙
340
341
342
343
344
345
346










347
348
349





350
351
352
353
354

















355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374

375
376
377
378
379
380
381
382
383



384
385
386
387
388







389

390

391
392
393
394
395
396
397
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507



508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581

582
583
584
585
586
587
588
589







+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




















+









+
+
+





+
+
+
+
+
+
+

+
-
+







		    }
		} else {
		    while (1) {
			str = a.shift()
			if (!str)
				break
			p = "CCDDEFFGGAAB".indexOf(str[0])
			if (p < 0)
				return // undefined
			o = str[1]
			switch (o) {
			case '#':
			case 'b':
				p += o == '#' ? 1 : -1
				o = Number(str[2])
				break
			default:
			o = Number(str[1])
			if (p < 0 || isNaN(o))
				return // null
				o = Number(str[1])
				break
			}
			if (isNaN(o))
				return // undefined
			t.push((o + 1) * 12 + p)	// C4 = 60
		    }
		}
		return t
	} // str2tab()

	// convert the list of <string number> ':' <fret number>
	function minfret(a) {
	    var	sf,
		sfa = a.split(' ')

		p_v.minfret = {}
		while (1) {
			sf = sfa.shift()
			if (!sf)
				break
			sf = sf.split(':')
			if (sf.length != 2)
				break		//fixme: error
			p_v.minfret[sf[0]] = sf[1]
		}
	} //minfret()

	for (i = 0; i < a.length; i++) {
		switch (a[i]) {
		case "clef=":
			e = a[i + 1]
			if (e != "tab")
				break
			a.splice(i, 1)
			// fall thru
		case "tab":
			a.splice(i, 1)
			i--
			ok = true
			break
		case "strings=":
			strs = a[++i]
			ok = true
			break
		case "nostems":
			p_v.pos.stm = abc2svg.C.SL_HIDDEN
			p_v.pos.gst = abc2svg.C.SL_HIDDEN
			break
		case "capo=":
			p_v.capo = Number(a[++i])
			break
		case "diafret=":
			i++
		case "diafret":
			p_v.diafret = true
			break
		case "minfret=":
			minfret(a[++i])
			break
		}
	}

	// define the elements of the tablature
	if (ok) {
		if (!strs && this.parse.tab) {		// if a global definition
			strs = this.parse.tab
			if (strs.indexOf("diafret") >= 0) {
				p_v.diafret = true
				strs = strs.replace(/\s*diafret\s*/, "")
			}
		}
		if (strs) {
			e = strs.slice(-1)
			if (strs[1] >= '1' && strs[1] <= '9')
			if (e >= '1' && e <= '9')
				tab = str2tab(strs.split(','))	// W.V.'s syntax
			else
				tab = abc2tab(strs)		// ABC syntax
			if (!tab) {
				this.syntax(1, "Bad strings in tablature")
				ok = false
			}
︙
415
416
417
418
419
420
421
422

423
424
425
426
427
428
429

430
431
432
433
434
435
436
607
608
609
610
611
612
613

614
615
616
617
618
619
620

621
622
623
624
625
626
627
628







-
+






-
+







		a.push("clef=")				// set the clef
		g = this.get_glyphs()
		if (tab.length == 3) {
			a.push('"tab3"')
			if (!g.tab3)
// SMuFL: -none-
				g.tab3 = '<text id="tab3"\
 x="-4,-4,-4" y="-4,3,10"\
 x="-2,-2,-2" y="-4,3,10"\
 style="font:bold 8px sans-serif">TAB</text>'
		} else if (tab.length == 4) {
			a.push('"tab4"')
			if (!g.tab4)
// SMuFL: \ue06e
				g.tab4 = '<text id="tab4"\
 x="-4,-4,-4" y="-8,1,10"\
 x="-3,-3,-3" y="-8,1,10"\
 style="font:bold 12px sans-serif">TAB</text>'
		} else if (tab.length == 5) {
			a.push('"tab5"')
			if (!g.tab5)
// SMuFL: -none-
				g.tab5 = '<text id="tab5"\
 x="-4,-4,-4" y="-11,-2,7"\
︙
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473

474
475
476
477
478
644
645
646
647
648
649
650

651
652
653
654
655
656
657
658
659
660
661
662
663

664
665
666
667
668
669







-













-
+





	of(a)
    }, // set_vp()

    set_hooks: function(abc) {
	abc.draw_symbols = abc2svg.strtab.draw_symbols.bind(abc, abc.draw_symbols)
	abc.set_format = abc2svg.strtab.set_fmt.bind(abc, abc.set_format);
	abc.set_stems = abc2svg.strtab.set_stems.bind(abc, abc.set_stems)
	abc.set_width = abc2svg.strtab.set_width.bind(abc, abc.set_width)
	abc.set_vp = abc2svg.strtab.set_vp.bind(abc, abc.set_vp)

	// define specific decorations used to force the string number
    var	decos = abc.get_decos()
	decos["1s"] = "0 nul 0 0 0"
	decos["2s"] = "0 nul 0 0 0"
	decos["3s"] = "0 nul 0 0 0"
	decos["4s"] = "0 nul 0 0 0"
	decos["5s"] = "0 nul 0 0 0"
	decos["6s"] = "0 nul 0 0 0"
	if (!user.nul)
		user.nul = function() {}

	abc.add_style("\n.bn{font:bold 8px sans-serif}")
	abc.add_style("\n.bn{font:bold 7px sans-serif}")
    } // set_hooks()
} // strtab

abc2svg.modules.hooks.push(abc2svg.strtab.set_hooks)
abc2svg.modules.strtab.loaded = true
Added modules/tropt.js.


























































































































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
// tropt.js - module to optimize the notes after transposition
//
// Copyright (C) 2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg.  If not, see <http://www.gnu.org/licenses/>.
//
// This module is loaded when "%%tropt" appears in a ABC source.

abc2svg.tropt = {

    // function called before start of the generation
    set_pitch: function(of, last_s) {
	if (last_s) {			// if not first time
		of(last_s)
		return
	}
    var	v, p_v, s, m, nt, p, a, np, na,
	C = abc2svg.C,
	vo_tb = this.get_voice_tb(),
	nv = vo_tb.length

	// check if some next note has the same pitch without any accidental
	function ok(s, p) {
	    var	nt, m

		while (s) {
			if (s.bar_type)
				return 1 //true
			if (s.type == C.NOTE) {
				for (m = 0; m <= s.nhd; m++) {
					nt = s.notes[m]
					if (nt.pit == p)
						return nt.acc
				}
			}
			s = s.next
		}
		return 1 //true
	} // ok()

	for (v = 0; v < nv; v++) {
		p_v = vo_tb[v]
		if (!p_v.tropt || !p_v.key.k_none)
			continue
		for (s = p_v.sym; s; s = s.next) {
			if (s.type != C.NOTE)
				continue
			for (m = 0; m <= s.nhd; m++) {
				nt = s.notes[m]
				if (nt.tie_s) {		// if end of tie
					nt.pit = nt.tie_s.pit
					continue
				}
				p = nt.pit % 7		// A..G
				a = nt.acc
				na = 3
				switch (a) {
				case -1:
					switch (p) {
					case 2:		// C
					case 5:		// F
						break	
					default:
						continue
					}
					np = nt.pit - 1
					break
				case -2:
					switch (p) {
					case 2:		// C
					case 5:		// F
						na = -1
						break
					}
					np = nt.pit - 1
					break
				case 1:
					switch (p) {
					case 1:		// B
					case 4:		// E
						break	
					default:
						continue
					}
					np = nt.pit + 1
					break
				case 2:
					switch (p) {
					case 1:		// B
					case 4:		// E
						na = 1
						break	
					}
					np = nt.pit + 1
					break
				default:
					continue
				}
				if (ok(s, np)) {
					nt.pit = np
					nt.acc = na
				}
			}
		}
	}
	of(last_s)
    }, // set_pitch()

    // set the tropt parameter
    do_pscom: function(of, text) {
	if (text.indexOf("tropt ") == 0)
		this.set_v_param("tropt", text.split(/[ \t]/)[1])
	else
		of(text)
    }, // do_pscom()

    // set the tropt parameter in the current voice
    set_vp: function(of, a) {
    var	i,
	curvoice = this.get_curvoice()

	for (i = 0; i < a.length; i++) {
		if (a[i] == "tropt=") {
			curvoice.tropt = this.get_bool(a[i + 1])
			break
		}
	}
	of(a)
    }, // set_vp()

    set_hooks: function(abc) {
	abc.do_pscom = abc2svg.tropt.do_pscom.bind(abc, abc.do_pscom)
	abc.set_pitch = abc2svg.tropt.set_pitch.bind(abc, abc.set_pitch)
	abc.set_vp = abc2svg.tropt.set_vp.bind(abc, abc.set_vp)
    }
} // tropt

abc2svg.modules.hooks.push(abc2svg.tropt.set_hooks)

// the module is loaded
abc2svg.modules.tropt.loaded = 1 //true
Changes to package.json.
1
2
3

4
5
6
7
8
9
10
11
12
13
14
15
16
17




18
19
20
21
22
23
24
1
2

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


-
+














+
+
+
+







{
  "name": "abc2svg",
  "version": "1.21.6",
  "version": "1.22.1",
  "description": "ABC music notation translator",
  "files": [
    "COPYING",
    "COPYING.LESSER",
    "MIDI-1.js",
    "Scc1t2/",
    "abc2odt",
    "abc2svg",
    "abc2svg-1.js",
    "abc2svg.ttf",
    "abcdoc-1.js",
    "abcemb-1.js",
    "abcemb1-1.js",
    "abcemb2-1.js",
    "abcjsc",
    "abcqjs",
    "abcmjs",
    "abcnode",
    "abctopdf",
    "abcweb-1.js",
    "abcweb1-1.js",
    "abcweb2-1.js",
    "ambitus-1.js",
    "break-1.js",
    "capo-1.js",
︙
39
40
41
42
43
44
45

46
47
48
49
50
51
52
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57







+







    "edit-pt_BR.js",
    "equalbars-1.js",
    "err-de.js",
    "err-en.js",
    "err-fr.js",
    "err-it.js",
    "err-pt_BR.js",
    "gamelan-1.js",
    "grid-1.js",
    "grid2-1.js",
    "grid3-1.js",
    "json-1.js",
    "jazzchord-1.js",
    "jianpu-1.js",
    "mdnn-1.js",
︙
74
75
76
77
78
79
80




81
82
83
84
85
86
87
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96







+
+
+
+







  ],
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "bin": {
    "abc2odt": "abc2odt",
    "abc2svg": "abc2svg",
    "abcjsc": "abcjsc",
    "abcqjs": "abcqjs",
    "abcmjs": "abcmjs",
    "abcnode": "abcnode",
    "abctopdf": "abctopdf"
  },
  "repository": {
    "type": "fossil",
    "url": "https://chiselapp.com/user/moinejf/repository/abc2svg"
  },
  "keywords": [
︙
Changes to toabc.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - toabc.js - convert ABC to ABC
//
// Copyright (C) 2016-2021 Jean-Francois Moine
// Copyright (C) 2016-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
225
226
227
228
229
230
231
232



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

249
250
251
252
253



254
255

256
257
258
259
260

261
262
263
264
265
266
267
268
269
270
271


272



273
274
275
276
277
278
279
280
281
282
283
284
285



286
287
288
289
290
291
292
225
226
227
228
229
230
231

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255

256
257
258
259

260
261
262
263
264

265
266
267
268
269
270
271
272
273
274
275

276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293

294
295
296
297
298
299
300
301
302
303







-
+
+
+
















+




-
+
+
+

-
+




-
+










-
+
+

+
+
+












-
+
+
+







			ln += (s.clef_oct_transp ? '^' : '+') + '8'
		else if (s.clef_octave == -7)
			ln += (s.clef_oct_transp ? '_' : '-') + '8'
		return "clef=" + ln
	} // clef_dump()

	function deco_dump(a_dd) {
	    var	i, n, dd
	    var	i, n, dd,
		ln = ""

//fixme: check if user deco
		for (i = 0; i < a_dd.length; i++) {
			dd = a_dd[i]
			n = dd.name
			if (dd.ty) {
				if (dd.ty != '@')
					line += '!' +dd.ty + n + '!'
				else
					line += '!@' + dd.x + ',' + dd.y + n + '!'
			} else {
				if (deco_l[n])
					line += deco_l[n]
				else
					line += '!' + n + '!'
			}
		}
		return ln
	} // deco_dump

	function dur_dump(dur, grace) {
	    var	d = 0,
		l = grace ? C.BLEN / 4 : ulen
		l = grace ? C.BLEN / 4 : ulen,
		ln = ""

		if (dur == l)
			return
			return ""
		while (1) {
			if (dur % l == 0) {
				dur /= l
				if (dur != 1)
					line += dur.toString()
					ln += dur.toString()
				break
			}
			dur *= 2
if (d > 6) {
abc2svg.print("% *** bad duration")
break
}
			d++
		}
		if (d > 0)
			line += "//////".slice(0, d)
			ln += "//////".slice(0, d)
		return ln
	} // dur_dump()

    var	ft_w = ['', 'Thin', 'ExtraLight', 'Light', 'Regular',
		'Medium', 'Demi', 'Bold', 'ExtraBold', 'Black']

	function font_def(fn, p) {
	    var	c, f,
		i = p.indexOf('$')

		abc.get_font(fn)		// used font
// fixme: one '$' only
		if (i >= 0) {
			c = p[i + 1]
			if (c >= '1' && c <= '9') {
				c = "u" + c
				f = cfmt[c + "font"]		// user font
				f.fid = abc.get_font(c).fid	// dump it!
//				f.fid = abc.get_font(c).fid	// dump it!
				f.fid = 100 + +c
				f.used = 1
			}
		}
		font_dump()		// dump the new fonts
	} // font_def()

	function font_dump() {
	    var	k, f, def
︙
300
301
302
303
304
305
306
307

308
309

310
311





















312
313
314

315
316
317
318
319
320
321
322
311
312
313
314
315
316
317

318
319

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345

346

347
348
349
350
351
352
353







-
+

-
+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
+
-







			if (!f.used)
				continue	// not used
			if (old_font[f.fid])
				continue	// already out
			old_font[f.fid] = true
			def = f.name || ""
			if (f.weight)
				def += f.weight
				def += ft_w[(f.weight / 100) | 0]
			if (f.style)
				def += f.style
				def += f.style[0].toUpperCase() + f.style.slice(1)
			if (!def)
				def = "*"
			def += ' ' + (f.size || "*")
			if (f.box)
				def += ' box'
			if (f.pad)
				def += ' padding=' + f.pad.toFixed(1)
			if (f.class)
				def += ' class=' + f.class
			if (f.wadj) {
				def += ' wadj='
				switch (f.wadj) {
				case 'spacing':
					def += 'space'
					break
				case 'spacingAndGlyphs':
					def += 'glyph'
					break
				default:
					def += 'none'
					break
				}
			}
			if (k[0] == "u")
				k = "setfont-" + k[1]
			abc2svg.print('%%' + k + ' ' +
			abc2svg.print('%%' + k + ' ' + def)
				def + ' ' + (f.size || "*"))
		}
	} // font_dump()

	function gch_dump(a_gch) {
	    var i, j, gch

		for (i = 0; i < a_gch.length; i++) {
︙
359
360
361
362
363
364
365
366


367
368
369
370
371
372
373
374
375
376
377








378
379
380
381
382
383
384
390
391
392
393
394
395
396

397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424







-
+
+











+
+
+
+
+
+
+
+







			l = hl[i]
			if (info[l])
				abc2svg.print(l + ':' + info[l].replace(/\n/g, '\n' + l + ':'))
		}
	} // header_dump()

	function key_dump(s, clef) {
	    var	ln
	    var	ln, i, a

		if (s.k_bagpipe) {
			bagpipe = true			// for grace notes
			ln = "K:H" + s.k_bagpipe
		} else if (s.k_drum) {
			ln = "K:P"
		} else if (s.k_none) {
			ln = "K:none"
		} else {
			ln = "K:"
			ln += k_tb[s.k_sf + 7 + mode_tb[s.k_mode][0]]
			ln += mode_tb[s.k_mode][1]
		}
		a = s.k_a_acc				// accidental list
		if (a) {
			if (s.k_exp)
				ln += ' exp'
			ln += ' '
			for (i = 0; i < a.length; i++)
				ln += note_dump({}, a[i])
		}

		if (clef && clef.clef_type != 'a')
			ln += ' ' + clef_dump(clef)
//fixme: to continue
		return ln
	} // key_dump()
︙
448
449
450
451
452
453
454
455



456
457
458
459

460
461
462
463
464
465
466
467
468

469
470

471
472
473
474
475
476

477
478

479
480
481

482
483
484
485
486
487
488





489
490
491
492
493

494
495

496
497

498
499
500
501

502
503

504
505

506
507

508
509
510
511
512

513
514

515
516

517
518
519


520
521
522


523
524
525

526
527
528

529
530

531
532
533
534
535
536
537
488
489
490
491
492
493
494

495
496
497
498
499
500

501
502
503
504
505
506
507
508
509

510
511

512
513
514
515
516
517

518
519

520
521
522

523
524
525





526
527
528
529
530
531
532
533
534

535
536

537
538

539
540
541
542

543
544

545
546

547
548

549
550
551
552
553

554
555

556
557
558
559
560
561
562
563
564
565


566
567
568
569

570
571
572

573
574
575
576
577
578
579
580
581
582
583







-
+
+
+



-
+








-
+

-
+





-
+

-
+


-
+


-
-
-
-
-
+
+
+
+
+




-
+

-
+

-
+



-
+

-
+

-
+

-
+




-
+

-
+


+



+
+

-
-
+
+


-
+


-
+


+







			if (s.a_meter[i].bot)
				ln += '/' + s.a_meter[i].bot
		}
		return ln
	} // meter_dump()

	function note_dump(s, note, tie_ch) {
	    var	p, j, sl, s2, a, d
	    var	p, j, sl, s2, a, d,
		ln = ""

		if (note.sls) {
			for (j = 0; j < note.sls.length; j++) {
				sl = note.sls[j];
				slti_dump(sl.ty, '(');
				ln += slti_dump(sl.ty, '(')
				s2 = sl.note.s
				if (s2.sl2)
					s2.sl2++
				else
					s2.sl2 = 1
			}
		}
		if (note.a_dd)
			deco_dump(note.a_dd)
			ln += deco_dump(note.a_dd)
		if (note.color)
			line += "!" + note.color + "!"
			ln += "!" + note.color + "!"
		a = note.acc
		if (typeof a == "object") {
			d = a[1]
			a = a[0]
			if (a > 0) {
				line += '^'
				ln += '^'
			} else {
				line += '_'
				ln += '_'
				a = -a
			}
			dur_dump(C.BLEN / 4 * a / d, true)
			ln += dur_dump(C.BLEN / 4 * a / d, true)
		} else {
			switch (a) {
			case -2: line += '__'; break
			case -1: line += '_'; break
			case 1: line += '^'; break
			case 2: line += '^^'; break
			case 3: line += '='; break
			case -2: ln += '__'; break
			case -1: ln += '_'; break
			case 1: ln += '^'; break
			case 2: ln += '^^'; break
			case 3: ln += '='; break
			}
		}
		p = note.pit
		if (p >= 23) {
			line += "abcdefg"[p % 7]
			ln += "abcdefg"[p % 7]
			if (p >= 30) {
				line += "'"
				ln += "'"
				if (p >= 37)
					line += "'"
					ln += "'"
			}
		} else {
			p += 7;			// for very low notes
			line += "ABCDEFG"[p % 7]
			ln += "ABCDEFG"[p % 7]
			if (p < 23) {
				line += ","
				ln += ","
				if (p < 16) {
					line += ","
					ln += ","
					if (p < 9)
						line += ","
						ln += ","
				}
			}
		}
		if (!tie_ch && note.tie_ty)
			slti_dump(note.tie_ty, '-')
			ln += slti_dump(note.tie_ty, '-')
		while (note.sl2) {
			line += ')';
			ln += ')'
			note.sl2--
		}
		return ln
	} // note_dump()

	function slti_dump(fl, ty) {
	    var	ln = ""

		if (fl & C.SL_DOTTED)
			line += ".";
		line += ty
			ln += '.'
		ln += ty
		switch (fl & 0x07) {
		case C.SL_ABOVE:
			line += "'"
			ln += "'"
			break
		case C.SL_BELOW:
			line += ','
			ln += ','
			break
		}
		return ln
	} // slti_dump()

//fixme: missing: '+' (master voice) and '*' (floating voice)
	function staves_dump(s) {
	    var	v, p_v, staff, ln,
		in_parenth,
		vn = [],
︙
690
691
692
693
694
695
696
697

698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714

715
716
717
718
719
720
721
736
737
738
739
740
741
742

743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759

760
761
762
763
764
765
766
767







-
+
















-
+







					' ' + s.repeat_k + ']'
		}
		if (s.tp)
			tuplet_dump(s)
		if (s.sls) {
			for (i = 0; i < s.sls.length; i++) {
				sl = s.sls[i];
				slti_dump(sl.ty, '(');
				line += slti_dump(sl.ty, '(')
				if (sl.is_note) {
					if (sl.note.sl2)
						sl.note.sl2++
					else
						sl.note.sl2 = 1
				} else {
					if (sl.note.s.sl2)
						sl.note.s.sl2++
					else
						sl.note.s.sl2 = 1
				}
			}
		}
		if (s.a_gch)
			gch_dump(s.a_gch)
		if (s.a_dd)
			deco_dump(s.a_dd)
			line += deco_dump(s.a_dd)
		if (s.color)
			line += "!" + s.color + "!"
		switch (s.type) {
		case C.BAR:
			if (s.beam_on)
				line += "!beamon!"
			if (s.bar_dotted)
︙
768
769
770
771
772
773
774
775

776
777
778
779
780
781
782
783
784
785

786
787
788
789
790
791
792
793
794
795
796

797
798
799
800
801

802
803
804

805
806
807
808
809
810
811
814
815
816
817
818
819
820

821
822
823
824
825
826
827
828
829
830

831
832
833
834
835
836
837
838
839
840
841

842
843
844
845
846

847
848
849

850
851
852
853
854
855
856
857







-
+









-
+










-
+




-
+


-
+







						s.p_v.pos.stm = C.SL_BELOW
						line += "[I:pos stem down]"
					}
				}
			}
			tie_ch = s.ti1
			if (s.notes.length == 1) {
				note_dump(s, s.notes[0], tie_ch)
				line += note_dump(s, s.notes[0], tie_ch)
			} else {
				for (i = 0; i < s.notes.length; i++) {
					if (!s.notes[i].tie_ty) {
						tie_ch = false
						break
					}
				}
				line += '['
				for (i = 0; i < s.notes.length; i++)
					note_dump(s, s.notes[i], tie_ch);
					line += note_dump(s, s.notes[i], tie_ch)
				line += ']'
			}
			if (s.grace) {
				if (bagpipe) {
					tmp = s.dur * 8
				} else {
					tmp = s.dur * 2
					if (s.prev || s.next)
						tmp *= 2
				}
				dur_dump(tmp, true)
				line += dur_dump(tmp, true)
			} else {
				tmp = s.notes[0].dur	// head duration
				if (s.trem2)
					tmp /= 2;
				dur_dump(tmp)
				line += dur_dump(tmp)
			}
			if (tie_ch)
				slti_dump(s.notes[0].tie_ty, '-');
				line += slti_dump(s.notes[0].tie_ty, '-')
			tmp = s.sl2
			while (tmp) {
				line += ')';
				tmp--
			}
			break
		case C.TEMPO:
︙
820
821
822
823
824
825
826
827
828

829
830
831
832
833
834
835
866
867
868
869
870
871
872


873
874
875
876
877
878
879
880







-
-
+







					abc2svg.print("%%writefields Q 1")
					cfmt.writefields += 'Q'
				}
			}
			tempo_dump(s)
			break
		case C.REST:
			line += s.invis ? 'x' : 'z';
			dur_dump(s.dur_orig)
			line += (s.invis ? 'x' : 'z') + dur_dump(s.dur_orig)
			break
		case C.SPACE:
			line += 'y'
			if (s.width != 10)
				line += s.width
			break
		case C.STAVES:
︙
896
897
898
899
900
901
902
903

904
905
906
907
908
909
910
941
942
943
944
945
946
947

948
949
950
951
952
953
954
955







-
+








	for (v = 0; v < nv; v++) {
		vo[v] = ""
		vti[v] = 0
	}

	if (info.Q) {
		abc2svg.print("Q: " + info.Q)
		abc2svg.print("Q:" + info.Q)
		for (s = tsfirst; s; s = s.ts_next) {
			if (s.time)
				break
			if (s.type == C.TEMPO)
				s.del = true
		}
	}
︙
934
935
936
937
938
939
940
941
942

943
944
945
946
947
948
949
979
980
981
982
983
984
985


986
987
988
989
990
991
992
993







-
-
+







		}
		line = "";

		// (all voices are synchronized on %%score)
		if (s.type != C.STAVES && s.time > vti[v]) {
//fixme: put 'X' if more than one measure
			if (s.time > vti[v] + 2) {
				line += 'x';
				dur_dump(s.time - vti[v]);
				line += 'x' + dur_dump(s.time - vti[v])
			}
			vti[v] = s.time
		}
		sym_dump(s)
		if (s.dur)
			vti[v] = s.time + s.dur
		if (s.next) {
︙
Changes to tohtml.js.
1
2
3

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

21
22
23
24
25
26
27
1
2

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

20
21
22
23
24
25
26
27


-
+
















-
+







// abc2svg - tohtml.js - HTML+SVG generation
//
// Copyright (C) 2014-2021 Jean-Francois Moine
// Copyright (C) 2014-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with abc2svg.  If not, see <http://www.gnu.org/licenses/>.

    var	init_done, pw, ml, mr, pkf, lkf
    var	init_done, pw, ml, mr, pkf, lkf, fn

// replace <>& by XML character references
function clean_txt(txt) {
	return txt.replace(/<|>|&.*?;|&/g, function(c) {
		switch (c) {
		case '<': return "&lt;"
		case '>': return "&gt;"
︙
145
146
147
148
149
150
151
152

153
154
155
156
157
158
159
145
146
147
148
149
150
151

152
153
154
155
156
157
158
159







-
+







			pw = npw
		psty += 'width:' + (npw - nml - nmr).toFixed(1) + 'px;'
	}
	return psty
}

// entry point from cmdline
abc2svg.abc_init = function() {
abc2svg.abc_init = function(args) {
    var cfmt = abc.cfmt()

	// output a header or footer
	function gen_hf(type, str) {
	    var	i, page,
		lcr = ["l", "c", "r"],
//fixme: handle font changes?
︙
178
179
180
181
182
183
184





185
186
187
188
189
190
191
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196







+
+
+
+
+







		abc2svg.print('</table>')
	}

	user.page_format = true

	// output the html header
	user.img_out = function(str) {
		if (init_done) {
			abc2svg.print(str)
			return
		}

		var	header = cfmt.header,
			footer = cfmt.footer,
			topmargin = cfmt.topmargin || "1cm",
			botmargin = cfmt.botmargin || "1cm",
			media_s = '@media print {\n\
	body {margin:0; padding:0; border:0}\n\
	.newpage {page-break-before: always}\n\
︙
247
248
249
250
251
252
253
254

255
256
257
258
259
260
261
252
253
254
255
256
257
258

259
260
261
262
263
264
265
266







-
+







// important for chrome and --headless (abctopdf)
		if (abc.page)
			abc2svg.print('@page{size:' +
				(cfmt.pagewidth / 96).toFixed(2) + 'in ' +
				(cfmt.pageheight / 96).toFixed(2) + 'in;margin:0}')

		abc2svg.print('</style>\n\
<title> ' + abc.parse.fname.replace(/.*\//, '')
<title>' + fn.replace(/.*\//,'')
			+ '</title>\n\
<body>')
		if (header || footer) {
			if (header)
				gen_hf("header", header)
			if (footer)
				gen_hf("footer", footer)
︙
269
270
271
272
273
274
275
276
277
278












279
280
281
282
283
284
285
274
275
276
277
278
279
280



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299







-
-
-
+
+
+
+
+
+
+
+
+
+
+
+







			init_done = 2		// with header/footer
		} else {
			init_done = 1
		}

		// output the first generated string
		abc2svg.print(str)

		// change the output function
		user.img_out = abc2svg.print
	}

	// get the main ABC source file name
	for (var i = 0; i < args.length; i++) {
	    var	a = args[i]

		if (a[0] == '-') {
			i++
			continue
		}
		fn = a
		break
	}
}

abc2svg.abc_end = function() {
    var	font_style = abc.get_font_style()

	if (!init_done)				// if empty document
︙
Changes to tonotes.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abc2svg - tonotes.js - convert ABC to a list of MIDI notes
//
// Copyright (C) 2021 Jean-Francois Moine
// Copyright (C) 2021-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
33
34
35
36
37
38
39
40

41

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58














59
60
61


62
63
64
65



66
67
68
69
70
71
72
33
34
35
36
37
38
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73


74
75
76
77
78

79
80
81
82
83
84
85
86
87
88







-
+

+
















-
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+



-
+
+
+







// The time and the durations are in 1/100s.
// Lines starting with '#' are comments.

// don't load tohtml.js
abc2svg.abc_init = function(args) {
}

// but load the sound generator
// but load the sound generator and the chord generator
abc2svg.loadjs('util/sndgen.js')
abc2svg.loadjs('util/chord.js')

abc2svg.abc_end = function() {
	function pit(v) {
		if (v - (v | 0))
			return v.toFixed(2)
		return v.toString()
	} // pit()

    var	e, t, vn,
	audio = ToAudio(),		// (in sndgen.js)
	po = {				// play object
		conf: {		// configuration
			speed: 1
		},
		tgen: 3600, 	// generate by (for) 1 hour
		get_time: function() { return -.3},	// (move the time origin)
		midi_ctrl: function(po, s, t) { },
		midi_ctrl: function(po, s, t) {
			abc2svg.print(' v:' + s.v
					+ '  MIDI control ' + s.ctrl + ' ' + s.val)
		}, // midi_ctrl()
		midi_prog: function(po, s) {
			if (s.instr)
				abc2svg.print(' v:' + s.v
					+ '  MIDI program '
					+ (s.chn + 1) + ' '
					+ s.instr)
			else
				abc2svg.print(' v:' + s.v
					+ '  MIDI channel ' + (s.chn + 1))
		}, // midi_prog()
		note_run: function(po, s, k, t, d) {
			abc2svg.print(' '+ (t * 100).toFixed(0) +
				'\t' + s.instr +
			abc2svg.print(' ' + (t * 100).toFixed(0) +
				'\t' + po.c_i[po.v_c[s.v]] +
				'\t' + pit(k) +
				'\t' + (d * 100).toFixed(0) +
				'\t' + vn[s.p_v.id])
		} // note_run()
		}, // note_run()
		v_c: [],		// voice to channel
		c_i: []			// channel to instrument
	},
	tunes = abc.tunes.slice(0)	// get a copy of the generated tunes

	// define the voice numbers
	function dvn(v_tb) {
	    var	i, n, p_v,
		v = 0
︙
Changes to toxhtml.js.
︙
237
238
239
240
241
242
243
244

245
246
247
248
249
250
251
237
238
239
240
241
242
243

244
245
246
247
248
249
250
251







-
+







			abc2svg.print('@page{size:' +
				(cfmt.pagewidth / 96).toFixed(2) + 'in ' +
				(cfmt.pageheight / 96).toFixed(2) + 'in;margin:0}')
		else
			abc2svg.print('@page{margin:' + topmargin + ' 0 ' +
							botmargin + ' 0}')
		abc2svg.print('</style>\n\
<title> ' + abc.parse.fname.replace(/.*\//, '') + '</title>\n\
<title>' + abc.parse.fname.replace(/.*\//, '') + '</title>\n\
</head>\n\
<body>')
		if (header)
			gen_hf("header", header)
		if (footer)
			gen_hf("footer", footer);

︙
Changes to util/abcdoc.js.
︙
69
70
71
72
73
74
75
76

77
78
79
80

81
82
83
84






85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
69
70
71
72
73
74
75

76
77
78
79

80




81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102




103
104
105
106
107
108
109







-
+



-
+
-
-
-
-
+
+
+
+
+
+
















-
-
-
-







		}
		return c
	})
}

// function to load javascript files
	abc2svg.loadjs = function(fn, relay, onerror) {
		var s = document.createElement('script');
		var s = document.createElement('script')
		if (/:\/\//.test(fn))
			s.src = fn		// absolute URL
		else
			s.src = jsdir + fn;
			s.src = jsdir + fn
		if (relay)
			s.onload = relay;
		s.onerror = onerror || function() {
			alert('error loading ' + fn)
		s.onload = relay
		s.onerror = function() {
			if (onerror)
				onerror(fn)
			else
				alert('error loading ' + fn)
		}
		document.head.appendChild(s)
	}

    // search the ABC tunes and add their rendering as SVG images
    function render() {
    var	i = 0, j, k, res, re,
	re_stop = /\n<|\n%.begin/g,	// stop on "<" and skip "%%begin"
	abc = new abc2svg.Abc(user);

	// get the ABC insertion mode
	if (page.indexOf('<script type="text/vnd.abc"') >= 0)
		re = /<script type="text\/vnd.abc"/g
	else
		re = /\n%abc|\nX:/g

	abc.tosvg('abcdoc', '%%bgcolor white\n\
%%rightmargin 0.8cm\n\
%%leftmargin 0.8cm\n\
%%topspace 0')
	for (;;) {

		// get the start of a ABC sequence
		res = re.exec(page)
		if (!res)
			break
		j = re.lastIndex - res[0].length;
︙
Changes to util/abcweb.js.
︙
47
48
49
50
51
52
53
54

55
56
57
58
59
60


61
62
63
64
65
66
67
68
69
70

71
72

73
74
75
76


77
78
79
80
81

82
83
84
85


























































































































































































































































































































































86
87
88
89
90
91
92
93

94
95
96


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113




114
115
116
117
118
119
120
47
48
49
50
51
52
53

54
55
56
57
58


59
60










61


62




63
64
65
66
67
68

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426

427
428


429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458







-
+




-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
-
-
+
-
-
-
-
+
+




-
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
+

-
-
+
+

















+
+
+
+







		alert("window error: " + msg.type + ' ' + msg.target.src)
	else
		alert("window error: " + msg)
	return false
}

    var	user,
	abcplay
	abcplay				// (usable for volume or tempo changes)

if (typeof abc2svg == "undefined")
    var abc2svg = {}

// function called when abc2svg is fully loaded
function dom_loaded() {
	abc2svg.mu = ""			// container of the current played tune

var	abc, src,
	a_inc = {},
	new_page = '',
	errtxt = '',
	html,			// set if some pure HTML in the file
	app = "abcweb",
	glopar = '',		// global parameters from URL
	playing,
	playconf = {
		onend: function() {
	abc2svg.abc_end = function() {}	// accept page formatting
			playing = false
		}

	},
	page,				// document source
	tune_lst = [],	// array of [tsfirst, voice_tb, info, cfmt] per tune
	jsdir = document.currentScript ?
	// get the path to the abc2svg scripts
	abc2svg.jsdir = document.currentScript ?
		    document.currentScript.src.match(/.*\//) :
		    (function() {
		     var s_a = document.getElementsByTagName('script')
			for (var k = 0; k < s_a.length; k++) {
				if (s_a[k].src.indexOf(app) >= 0)
				if (s_a[k].src.indexOf("abcweb") >= 0)
					return s_a[k].src.match(/.*\//) || ''
			}
			return ""	// ??
	})()

	// function to load javascript files
	abc2svg.loadjs = function(fn, relay, onerror) {
	    var	s = document.createElement('script')
		if (/:\/\//.test(fn))
			s.src = fn		// absolute URL
		else
			s.src = abc2svg.jsdir + fn
		s.onload = relay
		s.onerror = function() {
			if (onerror)
				onerror(fn)
			else
				alert('error loading ' + fn)
		}
		document.head.appendChild(s)
	} // loadjs()

// function called when abcweb-1.js is fully loaded
function dom_loaded() {
    var	abc, src, outb, err,
	a_inc = {},
	tune_lst = [],	// array of [tsfirst, voice_tb, info, cfmt] per tune
			// created on playback start
	html,			// set if some pure HTML in the file
	busy,
	playing,
	playconf = {
		onend: function() {
			playing = 0 //false
		}
	}

	// check which <div> became visible
	// if yes, generate it
	function visible() {
	    var	mu, r,
		wh = window.innerHeight || document.documentElement.clientHeight

		while (1) {
			mu = abc2svg.alldiv[0]
			if (!mu)
				break
			r = mu.d.getBoundingClientRect()
			if (r.top > wh)
				break
			musgen(mu)
			abc2svg.alldiv.shift()
		}
		if (abc2svg.alldiv.length) {
			if (!abc2svg.onscroll) {
				abc2svg.onscroll = visible
				window.addEventListener("scroll", visible)
			}
		} else {
			window.removeEventListener("scroll", visible)
		}
	} // visible()
	
	// get the custom properties of a HTML element
	// from 'class="<class>"' and from 'style="--<key>:<value>"'
	function get_p(e) {
	    var	i, j, k, r,
		o = '',
		sh = document.styleSheets,
		s = e.style

		// from class=".."
		c = e.getAttribute("class")
		if (c) {
			c = '.' + c				// selector
			for (i = 0; i < sh.length; i++) {
				r = sh[i].rules
				for (j = 0; j < r.length; j++) {
					if (r[j].selectorText == c)
						break
				}
				if (j < r.length)
					break
			}
			if (i < sh.length) {
				r = r[j]			// rule
				for (i = 0; i < r.style.length; i++) {
					k = r.style[i]
					if (k[0] == '-' && k[1] == '-')
						o += '%%' + k.slice(2) + ' '
							+ r.style.getPropertyValue(k)
							+ '\n'
				}
			}
		}

		// from style=".."
		for (i = 0; i < s.length; i++) {
			k = s[i]
			if (k[0] == '-' && k[1] == '-')
				o += '%%' + k.slice(2) + ' '
					+ s.getPropertyValue(k)
					+ '\n'
		}
		return o
	} // get_p()

	// move the musique sequences to <script> text/vnd.abc
	//	<mei> .. </mei>
	//	<anytag .. class="abc" .. > ..ABC.. </anytag>
	//	%abc-n ..ABC.. '<' with skip %%beginxxx .. %%endxxx
	//	X:n ..ABC.. '<' with skip %%beginxxx .. %%endxxx
	function move_music(src) {
	    var	re, res, i, j, k, t,
		re_stop = /\n<|\n%.begin[^\s]+/g,
		ss = 0,
		out = ""

		if (/<[^>]* class="[^"]*abc[^"]*/.test(src))	// "
			re = '<[^>]* class="[^"]*abc[^"]*'
		else
			re = '%abc-\\d|X:\\s*\\d'
		re = new RegExp('(^|\n)(' + re + ')', 'g')

		while (1) {
			res = re.exec(src)
			if (!res)
				break
			i = re.lastIndex - res[0].length // end of HTML
			if (i > ss) {
				out += src.slice(ss, i)	// copy HTML
				html = 1
			}
			t = res[2]
			if (t[0] == '<') {
				i = src.indexOf('>', i) + 1 // start of ABC
				j = res[2].indexOf(' ')	// get the tag
				t = res[2].slice(1, j)
				j = src.indexOf('</' + t + '>', i)
				ss = j + t.length + 4
			} else {		// %abc or X:

				// get the end of the ABC sequence
				// including the %%beginxxx/%%endxxx sequences
				//	(that may contain XML)
				re_stop.lastIndex = i
				while (1) {
					res = re_stop.exec(src)
					if (!res || res[0] == "\n<")
						break
					k = src.indexOf(res[0].replace("begin", "end"),
							re_stop.lastIndex)
					if (k < 0)
						break
					re_stop.lastIndex = k
				}
				if (!res || k < 0)
					j = src.length
				else
					j = re_stop.lastIndex - 1
				ss = j
			}
			out += '<script type="text/vnd.abc">\n'
				+ src.slice(i, j)
				+ '</script>\n'
			re.lastIndex = ss		// new HTML start
		}
		out += src.slice(ss)		// copy the last HTML sequence
		if (abc2svg.page && html)
			out += '\
<pre class="nop" style="background:#ff8080">\
Printing may be bad because the file contains pure HTML and %%pageheight\
</pre>\n'
		document.body.innerHTML = out
	} // move_music()

	// save the music sequences and replace them by empty <div>'s
	function save_music() {
	    var i, k, div, c, s, sa

		// save MEI sequences
		function save_spe(ty) {
		    var	s, div
			while (1) {
				s = document.getElementsByTagName(ty)[0]
				if (!s)
					break
				div = document.createElement('div')
				abc2svg.music.push({
					n: 'mus' + abc2svg.music.length,
					t: s.outerHTML,
					d: div
				})
				s.parentNode.replaceChild(div, s)
			}
		} // save_spe()

		// get the global parameters
		abc2svg.music = [{
			t: "",			// global parameters
			n: "mus0"
		}]

		// get the parameters from the query string of the URL
		k = location.search
		if (k) {
			k = k.substr(1).split("&")
			for (i = 0; i < k.length; i++)
				abc2svg.music[0].t += "%%"
					+ decodeURIComponent(k[i].replace('=', ' '))
					+ '\n'
		}

		// search the <script>'s of the HTML page
		// and replace the music <script>'s by empty <div>'s
		while (1) {
			sa = document.getElementsByTagName('script')
			for (i = 0; i < sa.length; i++) {
				s = sa[i]
				if (s.type == 'text/vnd.abc')
					break
			}
			if (i >= sa.length)
				break

			c = get_p(s)			// get custom properties
			div = document.createElement('div')
			if (s.text.indexOf('\nX:') < 0) {
				abc2svg.music[0].t += c + s.innerHTML // global
				if (!abc2svg.music[0].d)
					abc2svg.music[0].d = div
			} else {
				abc2svg.music.push({
					n: "mus" + abc2svg.music.length,
					t: c + s.innerHTML,
					d: div
				})
			}
			s.parentNode.replaceChild(div, s)
		}

		// special treatment for MEI
		save_spe('mei')
	} // save_music()

	// generate a music sequence
	// @mu = object { t: music source, d: <div> }
	function musgen(mu) {
	    var	t = mu.t

		if (busy) {
			mu.w = 1 //true
			return
		}
		busy = 1 //true

		// render a music sequence
		function render() {
		    var	i, j, e

			// start the generation
			outb = err = ""
			abc.tosvg(mu.n, t)	// music source

			abc2svg.abc_end()	// close the page if %%pageheight

			if (err)
				outb += '<pre class="nop" style="background:#ff8080">'
					+ err + "</pre>\n"

			if (abc.cfmt().with_source && outb)
				outb = '<pre class="source">'
					+ clean_txt(t)
					+ '</pre>\n\
<div class="source">\n'
					+ outb
					+ '</div>\n'
			
			mu.d.innerHTML = outb	// update the browser

			mu.d.addEventListener('click', abc2svg.playseq)
			e = mu.d.getElementsByTagName('svg')
			for (i = 0; i < e.length; i++) {
				j = e[i].getAttribute('class')
				if (!j)
					continue	// (page formatting)
				j = j.match(/tune(\d+)/)
				if (!j)
					continue
				j = j[1]		// tune number
				tune_lst[j] = null	// get new play references
			}

			// if some generation waiting, start it
			mu.w = busy = 0 //false
			for (i = 1; i < abc2svg.music.length; i++) {
				if (abc2svg.music[i].w) {
					musgen(abc2svg.music[i])
					break
				}
			}
		} // render()

		// load the possible %%abc-include files
		function include() {
		    var	i, j, fn, r,
			k = 0

			while (1) {
				i = t.indexOf('%%abc-include ', k)
				if (i < 0) {
					render()
					return
				}
				i += 14
				j = t.indexOf('\n', i)
				fn = t.slice(i, j).trim()
				if (!a_inc[fn])
					break
				k = j
			}

			// %%abc-include found: load the file
			r = new XMLHttpRequest()
			r.open('GET', fn, true)		// (async)
			r.onload = function() {
				if (r.status === 200) {
					a_inc[fn] = r.responseText
					if (abc2svg.modules.load(a_inc[fn], include))
						include()
				} else {
					a_inc[fn] = '%\n'
					alert('Error getting ' + fn + '\n' + r.statusText)
					include()
				}
			}
			r.onerror = function () {
				a_inc[fn] = '%\n'
				alert('Error getting ' + fn + '\n' + r.statusText)
				include()
			}
			r.send()
		} // include()

		// load the required modules and the include files before rendering
		if (abc2svg.modules.load(t, include))
			include()
	} // musgen()
//test
abc2svg.musgen = musgen

// -- abc2svg init argument
    user = {
	read_file: function(fn) {
		return a_inc[fn]
	}, // read_file()
	errmsg: function(msg, l, c) {	// get the errors
		errtxt += clean_txt(msg) + '\n'
		err += clean_txt(msg) + '\n'
	},
	img_out: function(str) {	// image output
		new_page += str
	img_out: function(p) {		// image output
		outb += p
	}
    }

// replace <>& by XML character references
function clean_txt(txt) {
	return txt.replace(/<|>|&.*?;|&/g, function(c) {
		switch (c) {
		case '<': return "&lt;"
		case '>': return "&gt;"
		case '&': return "&amp;"
		}
		return c
	})
}

	// function called on click on the music
	abc2svg.playseq = function(evt) {
		if (playing) {		// stop playing
			abcplay.stop()
			return
		}
	    var	i, j,
		svg = evt.target,
		e = svg			// keep the clicked element

		// search if click in a SVG image
		while (svg.tagName != 'svg') {
			svg = svg.parentNode
︙
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172









173

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378

379
380

381
382
383
384
385
386

387

388
389
390


391
392
393
394
395
396
397
398
399













400
401
402
403
404



405
406
407

408
409
410

411
412



























413
414

489
490
491
492
493
494
495





496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514

515
516
517
518







































































































































































































519
520
521
522
523

524
525
526
527
528
529
530
531

532
533


534
535









536
537
538
539
540
541
542
543
544
545
546
547
548
549
550



551
552
553



554



555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585

586







-
-
-
-
-










+
+
+
+
+
+
+
+
+
-
+



-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-



+

-
+






+
-
+

-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
-
+
+
+
-
-
-
+
-
-
-
+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+

		// check if click on a music symbol
		// (this works when 'follow' is active)
		s = tune_lst[i][0]		// first symbol of the tune
		i = e.getAttribute('class')
		if (i)
			i = i.match(/abcr _(\d+)_/)
		if (playing) {
			abcplay.stop();
			if (!i)
				return
		}
		if (i) {
			i = i[1]		// symbol offset in the source
			while (s && s.istart != i)
				s = s.ts_next
			if (!s) {		// fixme: error ?!
				alert("play bug: no such symbol in the tune")
				return
			}
		}

		// keep the container of the tune being played
		while (s && !s.fname)		// (no .fname in the 1st %%staves)
			s = s.ts_next
		for (i = 1; i < abc2svg.music.length; i++) {
			if (abc2svg.music[i].n == s.fname)
				break
		}
		abc2svg.mu = abc2svg.music[i]	// current container

		playing = true
		playing = 1 //true
		abcplay.play(s, null)
	} // playseq()

	// convert HTML to ABC
	function toabc(s) {
		return s.replace(/&gt;/g, '>')
			.replace(/&lt;/g, '<')
			.replace(/&amp;/g, '&')
			.replace(/[ \t]+(%%|.:)/g, '$1')
	} // toabc()

// function to load javascript files
	abc2svg.loadjs = function(fn, relay, onerror) {
		var s = document.createElement('script');
		if (/:\/\//.test(fn))
			s.src = fn		// absolute URL
		else
			s.src = jsdir + fn;
		if (relay)
			s.onload = relay;
		s.onerror = onerror || function() {
			alert('error loading ' + fn)
		}
		document.head.appendChild(s)
	}

function render() {

	// search the ABC tunes,
	// replace them by SVG images with play on click

//search in page
// 1- <script type="text/vnd.abc"> ..ABC.. <script>
// 2- <anytag .. class="abc" .. > ..ABC.. </anytag>
// 3- %abc-n ..ABC.. '<' with skip %%beginxxx .. %%endxxx
// 4- X:n ..ABC.. '<' with skip %%beginxxx .. %%endxxx

    var	re, src, res,
	ss = 0,
	re_stop = /\n<|\n%.begin[^\s]+/g

	// extract a music sequence
	// @re is the regular expression starting a music sequence
	function extract() {
	    var	i, j, k, t

		res = re.exec(page)
		if (!res) {

			// alert about bad print
			if (abc2svg.page && html) {
				new_page += '\
<pre class="nop" style="background:#ff8080">\
Printing may be bad because the file contains pure HTML and %%pageheight\
</pre>\n'
				errtxt = ""
			}

			document.body.innerHTML = new_page // browser page update
				+ page.slice(ss)	// HTML end
			window.onclick = abc2svg.playseq	// prepare for play on click
			return				// done
		}
		i = re.lastIndex - res[0].length	// start of music sequence
		if (i > ss) {
			new_page += page.slice(ss, i)		// HTML copy
			html = 1
		}
		t = res[2]
		if (t[0] == '<') {
			t = t.slice(1, 4)
			switch (t) {
			case "scr":			// <script
				i = page.indexOf('>', i)
				i = page.indexOf('\n', i) + 1
				j = page.indexOf('</script>', i)
				ss = j + 10
				break
			default:			// < .. class="abc"
				j = i

				i = res[2].index(' ', i) // get the tag
				t = res[2].slice(1, i)

				i = page.indexOf('>', j)
				i = page.indexOf('\n', i) + 1
				new_page += page.slice(j, i) // keep the element
				j = page.indexOf('</' + t + '>', i)
				ss = j + t.length + 4
				break
			}
		} else {		// %abc or X:

			// get the end of the ABC sequence
			// including the %%beginxxx/%%endxxx sequences
			re_stop.lastIndex = i
			while (1) {
				res = re_stop.exec(page)
				if (!res || res[0] == "\n<")
					break
				k = page.indexOf(res[0].replace("begin", "end"),
						re_stop.lastIndex)
				if (k < 0)
					break
				re_stop.lastIndex = k
			}
			if (!res || k < 0)
				j = page.length
			else
				j = re_stop.lastIndex - 1
			ss = j
		}
		if (j < 0) {
//fixme			error
console.log('error i/j '+i+' '+j)
			return
		}

		re.lastIndex = ss		// new HTML start
//		src = page.slice(i, j)		// music sequence
//		if (abc2svg.modules.load(src, render1))
//			render1()
//	} // extract()

	// render one text music sequence
	// @page is the content of the page
	// @ss and @se are the start and end indexes of the music sequence
	// i,j are the start and stop of the music inside the page source
//	function render1() {
		abc.tosvg(app, page, i, j)
		abc2svg.abc_end()	// close the page if %%pageheight

		if (errtxt) {
			new_page += '<pre class="nop" style="background:#ff8080">'
					+ errtxt + "</pre>\n"
			errtxt = ""
		}

		// try the next sequence
		extract()
	} // render1()

	// get the ABC insertion mode
	if (/<script[^t>]*type="text\/vnd.abc"/.test(page))
		re = '<script[^t>]*type="text\/vnd.abc"'
	else if (/<[^>]* class="[^"]*abc[^"]*/.test(page))	// "
		re = '<[^>]* class="[^"]*abc[^"]*'
	else
		re = '%abc-\\d|X:\\s*\\d'

	re = new RegExp('(^|\n)(' + re + ')', 'g')

	// prepare the generation of the music sequences
	abc = new abc2svg.Abc(user)
	if (typeof follow == "function") // initialize the play follow function
		follow(abc, user, playconf)
	abc.tosvg(app, glopar)

	extract()			// start
} // render()

	// load the %%abc-include files
	function include() {
	    var	i, j, fn, r,
		k = 0

		while (1) {
			i = page.indexOf('%%abc-include ', k)
			if (i < 0) {
				render()
				return
			}
			i += 14
			j = page.indexOf('\n', i)
			fn = page.slice(i, j).trim()
			if (!a_inc[fn])
				break
			k = j
		}

		// %%abc-include found: load the file
		r = new XMLHttpRequest()
		r.open('GET', fn, true)		// (async)
		r.onload = function() {
			if (r.status === 200) {
				a_inc[fn] = r.responseText
				if (abc2svg.modules.load(a_inc[fn], include))
					include()
			} else {
				a_inc[fn] = '%\n'
				alert('Error getting ' + fn + '\n' + r.statusText)
				include()
			}
		}
		r.onerror = function () {
			a_inc[fn] = '%\n'
			alert('Error getting ' + fn + '\n' + r.statusText)
			include()
		}
		r.send()
	} // include()

	// --- dom_loaded() main code ---

	// load the abc2svg core if not done by <script>
	src = document.body.innerHTML
	if (!abc2svg.Abc) {
		abc2svg.loadjs(document.body.innerHTML.indexOf("<mei ") >= 0 ?
		abc2svg.loadjs(src.indexOf("<mei ") >= 0 ?
					"mei2svg-1.js" :
					"abc2svg-1.js",
						dom_loaded)
		return
	}

	if (src.indexOf('type="text/vnd.abc"') < 0)
	window.removeEventListener("load", dom_loaded)
		move_music(src)

	// use the query string of the URL for global parameters
	{
	save_music()	// save the music sequences and replace them by <div>

	    var	i, j,
		k = location.search.substr(1).split("&")

	    for (i = 0; i < k.length; i++) {
		j = k[i].split('=')
		if (j[0])
			glopar += "%%" + j[0] + " "
				+ decodeURIComponent(j[1]) + '\n'
	    }
	// initialize the generation
	abc = new abc2svg.Abc(user)
	if (typeof follow == "function")	// if snd-1.js loaded
		follow(abc, user, playconf)	// initialize the play follow
	if (abc2svg.music[0].t) {
		abc.tosvg(abc2svg.music[0].n,	// global definitions
			  abc2svg.music[0].t)
		if (abc.cfmt().with_source
		 && abc.cfmt().with_source.indexOf('nohead') < 0) {
			abc2svg.music[0].d.innerHTML = '<pre class="source">'
					+ clean_txt(abc2svg.music[0].t)
					+ '</pre>\n'
		}
	}

	// accept page formatting
	abc2svg.abc_end = function() {}

	// create a list of all <div>'s
	abc2svg.alldiv = []
	for (var i = 1; i < abc2svg.music.length; i++)
	// load the included files if any
	page = document.body.innerHTML		// initial page

		abc2svg.alldiv.push(abc2svg.music[i])
	// load the required modules and the include files before rendering
	if (abc2svg.modules.load(page, include))
		include()
	visible()
} // dom_loaded()

// ---- interface for changing the music source and redisplaying it ----
// get the music source of a <div> element
abc2svg.get_music = function(d) {
    var	i, mu

	for (var i = 1; i < abc2svg.music.length; i++) {
		mu = abc2svg.music[i]
		if (mu.d == d)
			return mu.t
	}
//	return ""
} // get_music()

// set the music source of a <div> element and redisplay it
abc2svg.set_music = function(d, t) {
    var	i, mu

	for (var i = 1; i < abc2svg.music.length; i++) {
		mu = abc2svg.music[i]
		if (mu.d == d) {
			mu.t = t
			abc2svg.musgen(mu)
			break
		}
	}
} // set_music()

// wait for the scripts to be loaded
window.addEventListener("load", dom_loaded)
window.addEventListener("load", dom_loaded, {once:true})
Changes to util/abcweb1.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// abcweb1-1.js file to include in html pages with abc2svg-1.js
//
// Copyright (C) 2019-2021 Jean-Francois Moine
// Copyright (C) 2019-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
218
219
220
221
222
223
224
225
226
227
228






229
230
231
232
233
234
235
218
219
220
221
222
223
224




225
226
227
228
229
230
231
232
233
234
235
236
237







-
-
-
-
+
+
+
+
+
+







	// function to load javascript files
	abc2svg.loadjs = function(fn, relay, onerror) {
	    var	s = document.createElement('script')
		if (/:\/\//.test(fn))
			s.src = fn		// absolute URL
		else
			s.src = jsdir + fn
		if (relay)
			s.onload = relay
		s.onerror = onerror || function() {
			alert('error loading ' + fn)
		s.onload = relay
		s.onerror = function() {
			if (onerror)
				onerror(fn)
			else
				alert('error loading ' + fn)
		}
		document.head.appendChild(s)
	} // loadjs()

	// build a list of the tunes
	abc2svg.get_sel = function() {
	    var	j, k,
︙
338
339
340
341
342
343
344
345

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363





364
365
366
367
368
369
370
340
341
342
343
344
345
346

347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377







-
+


















+
+
+
+
+







			errtxt = ""
		}

		// add the menu
		new_page += '\
<div id="dd" class="dd nop">\
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" class="db">\
<path d="M4 6h15v2.5H4zm0 5h15v2.5H4zm0 5h15v2.5H4z" fill=black/>\
<path d="M4 6h15v2.5H4zm0 5h15v2.5H4zm0 5h15v2.5H4z" fill="black"/>\
</svg>\
<div id="dc" class="dc">\
<label id="edit" onclick="abc2svg.src_edit()">Source edit</label>\
<label id="list" onclick="abc2svg.get_sel()">Tune list</label>\
<label id="play" onclick="abc2svg.st_scroll()">Scroll</label>\
</div>\
</div>\
<label id="ss" class="dd nop" onclick="abc2svg.st_scroll()">Scroll<br/>stop</label>'

		// change the page
		try {
			document.body.innerHTML = new_page
		} catch (e) {
			alert("abc2svg bad generated SVG: " + e.message +
				"\nStack:\n" + e.stack)
			return
		}

		// add the event handlers
	   var	elts = document.getElementsByTagName('svg')
		for (var i = 0; i < elts.length; i++)
			elts[i].addEventListener('click', click)

		// update the menu
		setTimeout(function() {

			// remove scroll in menu if play or not a big tune
			if (typeof AbcPlay != "undefined"
			 || document.documentElement.scrollHeight <= window.innerHeight)
				document.getElementById("play").style.display = "none"
︙
419
420
421
422
423
424
425
426
427


428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445

446
447
448
449







450


451
452
453
454
455
456
457
426
427
428
429
430
431
432


433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451

452




453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469







-
-
+
+

















-
+
-
-
-
-
+
+
+
+
+
+
+

+
+







		abc2svg.loadjs("abc2svg-1.js", dom_loaded)
		return
	}

	// get the page content
	page = fix_abc(document.body.innerHTML)

	// mouse/tap events
	window.onclick = function(evt) {
	// click on a SVG element
	function click(evt) {
		if (playing) {			// stop playing
			abcplay.stop()
			return
		}

	    var	e, s, j,
		c = evt.target

		// remove the menu if active
		e = document.getElementById("dc")
		if (e && e.classList.contains("show")) {
			e.classList.remove("show") // remove the menu
			return
		}

		// search if click in a SVG image
		e = c				// keep the clicked element
		while (c && c.tagName != 'svg')
		while (1) {
			c = c.parentNode
		if (!c)
			return

			if (c == document)
				return
			if (c.tagName.toLowerCase() == 'svg')
				break
			c = c.parentNode
		}

		c = c.getAttribute('class')
		if (!c)
			return

		// if click in the menu button, show the menu
		if (c == "db") {
			e = document.getElementById("dc")
			e.classList.toggle("show")
			return
		}
︙
497
498
499
500
501
502
503
504

505
506
507
508
509
510
511
512
513
514
515
509
510
511
512
513
514
515

516
517
518
519
520
521
522
523
524
525
526
527







-
+











				alert("play bug: no such symbol in the tune")
				return
			}
		}

		playing = true
		abcplay.play(s, null)
	} // onclick()
	} // click()

	// accept page formatting
	abc2svg.abc_end = function() {}

	// load the required modules, then render the music
	if (abc2svg.modules.load(page, include))
		include()
} // dom_loaded()

// wait for the scripts to be loaded
window.addEventListener("load", dom_loaded)
Changes to util/chord.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// chord.js - generation of accompaniment
//
// Copyright (C) 2020-2021 Jean-Francois Moine and Seymour Shlien
// Copyright (C) 2020-2022 Jean-Francois Moine and Seymour Shlien
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
94
95
96
97
98
99
100
101
102
103



104
105
106
107
108
109






110
111
112
113
114
115
116
117

118
119
120
121
122
123
124
125
126
127
128
129





130
131
132
133
134
135
136
94
95
96
97
98
99
100



101
102
103

104
105
106


107
108
109
110
111
112
113

114
115
116
117
118

119


120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141







-
-
-
+
+
+
-



-
-
+
+
+
+
+
+

-





-
+
-
-










+
+
+
+
+







			case '-': v -= 12; break
			}
			r.push(v)
		}
		return r
	} // chcr()

	// (quite the same as abc2svg.cs_filter() but with otext)
	function filter(a_cs, sel) {
	    var	i, cs,
	// get the playback part of the first chord symbol
	function filter(a_cs) {
	    var	i, cs, t
		tcs = ""

		for (i = 0; i < a_cs.length; i++) {
			cs = a_cs[i]
			if (cs.type == 'g')
				tcs += cs.otext
			if (cs.type != 'g')
				continue
			t = cs.otext
			if (t.slice(-1) == ')')		// if alternate chord
				t = t.replace(/\(.*/, '') // remove it
			return t.replace(/\(|\)|\[|\]/g,'') // remove ()[]
		}
		return tcs.replace(sel ? abc2svg.cs_sel1 : abc2svg.cs_sel0, '')
	} // filter()

	// generate a chord
	function gench(sb) {
	    var	r, ch, b, m, n, not,
		a = filter(sb.a_gch, cfmt.altchord).
		a = filter(sb.a_gch),
			match(/([A-G])([#♯b♭]?)([^/]*)\/?(.*)/),
			// a[1] = note, a[2] = acc, a[3] = type, a[4] = bass
		s = {
			v: vch.v,
			p_v: vch,
			type: C.NOTE,
			time: sb.time,
			notes: []
		}

		if (!a)
			return
		a = a.match(/([A-GN])([#♯b♭]?)([^/]*)\/?(.*)/)
			// a[1] = note, a[2] = acc, a[3] = type, a[4] = bass
		if (!a)
			return

		r = abc2svg.letmid[a[1]]		// root
		if (r == undefined) {
			if (a[1] != "N")
				return
			s.type = C.REST			// ("N") = no chord
			ch = [0]
			r = 0
︙
167
168
169
170
171
172
173


174
175
176
177
178
179
180
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187







+
+







		if (b == undefined)
			b = 0
		ch = chcr(b, ch)

		// generate the notes of the chord
		n = ch.length
		r += trans
		if (sb.p_v.tr_snd)
			r += sb.p_v.tr_snd
		for (m = 0; m < n; m++) {
			not = {
				midi: r + ch[m]
			}
			s.notes.push(not)
		}
		s.nhd = n - 1
︙
206
207
208
209
210
211
212









213
214
215
216
217
218
219
220








221
222
223

224
225
226
227

228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233



234
235
236
237
238
239
240
241
242


243
244
245
246

247









248
249
250
251
252
253
254







+
+
+
+
+
+
+
+
+





-
-
-
+
+
+
+
+
+
+
+

-
-
+



-
+
-
-
-
-
-
-
-
-
-







//fixme: no inversion
			chnm[k] = [ vch ]
		}
	} else {
		chnm = abc2svg.ch_names
	}

	// define the MIDI channel
	k = 0
	for (i = 0; i < voice_tb.length; i++) {
		if (k < voice_tb[i].chn)
			k = voice_tb[i].chn
	}
	if (k == 8)
		k++			// skip the channel 10

	// create the chord voice
	vch = {
		v: voice_tb.length,
		id: "_chord",
		time: 0,
		sym: {			// dummy symbol to simplify chord insersion
			type: C.REST,
			time: 0
		sym: {
			type: C.BLOCK,
			subtype: "midiprog",
			chn: k + 1,
			instr: cfmt.chord.prog || 0,
			time: 0,
			ts_prev: first,
			ts_next: first.ts_next
		},
		instr: cfmt.chord.prog || 0,
		vol: cfmt.chord.vol || .7
		vol: cfmt.chord.vol || .6	// (external default 76.2)
	}
	vch.last_sym = vch.sym
	voice_tb.push(vch)

	first.ts_next = vch.sym
	// define the MIDI channel
	k = 0
	for (i = 0; i < vch.v; i++) {
		if (k < voice_tb[i].chn)
			k = voice_tb[i].chn
	}
	if (k == 8)
		k++			// skip the channel 10
	vch.chn = k + 1

	// loop on the symbols and add the accompaniment chords
	gchon = cfmt.chord.gchon
	s = first
	while (1) {
		if (!s.ts_next) {
			if (gchon)
︙
Changes to util/follow.js.
1
2
3
4
5
6

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47













48
49
50
51

52
53
54

55
56
57

58
59

60
61
62

63
64
65

66
67
68
69
70
71










72
73
74
75
76
77
78
79
80
1
2
3
4
5

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


25
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40
41




42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

58
59
60

61



62


63
64
65

66



67






68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86





-
+


















-
-
+







-









-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+



-
+


-
+
-
-
-
+
-
-
+


-
+
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+









// follow-1.js - file included in snd-1.js
//
// This script permits to follow the notes while playing.
// Scrolling the music may be disabled setting 'no_scroll' in the window object.
//
// Copyright (C) 2015-2020 Jean-Francois Moine
// Copyright (C) 2015-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with abc2svg.  If not, see <http://www.gnu.org/licenses/>.

// init
function follow(abc, user, playconf) {
var	ref = [],
	keep_types = {
    var	keep_types = {
		note: true,
		rest: true
	}

user.anno_stop = function(type, start, stop, x, y, w, h) {
	if (!keep_types[type])
		return
	ref[start] = stop;		// keep the source reference

	// create a rectangle
	abc.out_svg('<rect class="abcr _' + start + '_" x="');
	abc.out_sxsy(x, '" y="', y);
	abc.out_svg('" width="' + w.toFixed(2) +
		'" height="' + abc.sh(h).toFixed(2) + '"/>\n')
}

	playconf.onnote = function(i, on) {
	    var	b, x, y,
		elts = document.getElementsByClassName('_' + i + '_')
		if (elts && elts[0]) {
			elts[0].style.fillOpacity = on ? 0.4 : 0
	    var	b, i, e, elts,
		x = 0,
		y = 0

		if (abc2svg.mu)			// if many tunes with same offsets
			elts = abc2svg.mu.d.getElementsByClassName('_' + i + '_')
		else
			elts = document.getElementsByClassName('_' + i + '_')
		if (!elts || !elts.length)
			return			// no symbol?
		e = elts[0]

		e.style.fillOpacity = on ? 0.4 : 0

			// scroll for the element to be in the screen
			if (on && !window.no_scroll) {	
				b = elts[0].getBoundingClientRect()
				b = e.getBoundingClientRect()

				// normal
				if (b.top < 0)
				if (b.top < 0
					y = window.scrollY + b.top -
							window.innerHeight / 2
				else if (b.bottom > window.innerHeight)
				 || b.bottom > window.innerHeight * .8)
					y = window.scrollY + b.bottom +
							window.innerHeight / 2
					y = b.top - window.innerHeight * .3

				// single line
				if (b.left < 0)
				if (b.left < 0
					x = window.scrollX + b.left -
							window.innerWidth / 2
				else if (b.right > window.innerWidth)
				 || b.right > window.innerWidth * .8)
					x = window.scrollX + b.right +
							window.innerWidth / 2
				if (x != undefined || y != undefined)
					window.scrollTo(x || 0, y || 0)
			}
		}
					x = b.left - window.innerWidth * .3
				if (x || y)
					window.scrollBy({
						top: y,
						left: x,
						behavior: (x < 0 || y)
								? 'instant'
								: 'smooth'
					})
			}
	}
} // follow()

// create the style of the rectangles
(function () {
    var	sty = document.createElement("style")
	sty.innerHTML = ".abcr {fill: #d00000; fill-opacity: 0; z-index: 15}"
	document.head.appendChild(sty)
})()
Changes to util/play.js.
︙
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
115
116
117
118
119
120
121









122
123
124
125
126
127
128







-
-
-
-
-
-
-
-
-








		// set the current output changing the play functions
		current = out[o] == 'sf2' ? audio5 : midi5;
		abcplay.play = current.play;
		abcplay.stop = current.stop
		if (current.set_output)
			current.set_output(out[o]);
		if (abc2svg.pwait) {
			if (typeof abc2svg.pwait == "boolean") {
				abc2svg.pwait = function() {
					abcplay.play(init.istart,
							init.i_iend, init.a_e)
				}
			}
			return
		}
		abcplay.play(init.istart, init.i_iend, init.a_e);
	} // play2()

	// set default configuration values
	conf.gain = 0.7;
	conf.speed = 1;

︙
Changes to util/sndaud.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// sndaud.js - audio output using HTML5 audio
//
// Copyright (C) 2019-2021 Jean-Francois Moine
// Copyright (C) 2019-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
123
124
125
126
127
128
129
130

131
132
133
134
135
136


137
138
139
140
141
142
143
144
145

146
147

148
149
150
151
152
153
154
155

156
157
158
159
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
123
124
125
126
127
128
129

130
131
132
133
134


135
136
137
138
139
140
141
142
143
144

145
146

147
148
149
150
151
152
153
154

155
156
157
158
159
160
161
162
163
164
165
166

167
168
169
170
171
172
173
174







-
+




-
-
+
+








-
+

-
+







-
+











-
+







		a = b.getChannelData(0)		// destination = array of float32

		for (i = 0; i < s.length; i++)
			a[i] = s[i] / 196608	// volume divided by 6
	}

	// create all notes of an instrument
	function sf2_create(instr) {
	function sf2_create(instr, sf2par, sf2pre) {	// parser, presets

		// get the instrument parameters
		// adapted from getInstruments() in sf2-parser.js
		function get_instr(i) {
		    var	instrument = parser.instrument,
			zone = parser.instrumentZone,
		    var	instrument = sf2par.instrument,
			zone = sf2par.instrumentZone,
			j = instrument[i].instrumentBagIndex,
			jl = instrument[i + 1]
				? instrument[i + 1].instrumentBagIndex
				: zone.length,
			info = []

			while (j < jl) {
				instrumentGenerator =
					parser.createInstrumentGenerator_(zone, j)
					sf2par.createInstrumentGenerator_(zone, j)
//				instrumentModulator =
//					parser.createInstrumentModulator_(zone, j)
//					sf2par.createInstrumentModulator_(zone, j)

				info.push({
					generator: instrumentGenerator.generator,
//					modulator: instrumentModulator.modulator
				})
				j++
			}
//console.log('instr: '+instrument[i].instrumentName)
//console.log('created instr: '+instrument[i].instrumentName)
		return {
//			name: instrument[i].instrumentName,
			info: info
		}
	} // get_instr()

	// sf2_create
	    var i, j, k, sid, gen, parm, gparm, sample, infos,
		sampleRate, scale,
		b = instr >> 7,			// bank
		p = instr % 128,		// preset
		pr = presets
		pr = sf2pre

		rates[instr] = []

		// search the bank:preset
		for (i = 0; i < pr.length; i++) {
			gen = pr[i].header
			if (gen.preset == p
︙
221
222
223
224
225
226
227
228
229


230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246

247
248

249
250
251
252
253
254
255
256
257

258
259
260

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279





280
281
282
283
284
285
286
287
288
289
290

































































291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306

307
308
309
310
311
312
313
221
222
223
224
225
226
227


228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245

246
247

248
249
250
251
252
253
254
255
256

257
258
259

260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275




276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371

372
373
374
375
376
377
378
379







-
-
+
+
















-
+

-
+








-
+


-
+















-
-
-
-
+
+
+
+
+











+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+















-
+







			if (gen.sampleModes && gen.sampleModes.amount & 1)
				parm.sm = 1

			if (!gen.sampleID)	// (empty generator!)
				continue

			sid = gen.sampleID.amount
			sampleRate = parser.sampleHeader[sid].sampleRate
			sample = parser.sample[sid]
			sampleRate = sf2par.sampleHeader[sid].sampleRate
			sample = sf2par.sample[sid]
			parm.buffer = ac.createBuffer(1,
						sample.length,
						sampleRate)

			parm.hold += parm.attack
			parm.decay += parm.hold

			// sustain > 40dB is not audible
			if (parm.sustain >= .4)
				parm.sustain = 0.01	// must not be null
			else
				parm.sustain = 1 - parm.sustain / .4

			sample_cp(parm.buffer, sample)

			if (parm.sm) {
				parm.loopStart = parser.sampleHeader[sid].startLoop /
				parm.loopStart = sf2par.sampleHeader[sid].startLoop /
					sampleRate
				parm.loopEnd = parser.sampleHeader[sid].endLoop /
				parm.loopEnd = sf2par.sampleHeader[sid].endLoop /
					sampleRate
			}

			// define the notes
			scale = (gen.scaleTuning ?
					gen.scaleTuning.amount : 100) / 100,
			tune = (gen.coarseTune ? gen.coarseTune.amount : 0) +
				(gen.fineTune ? gen.fineTune.amount : 0) / 100 +
				parser.sampleHeader[sid].pitchCorrection / 100 -
				sf2par.sampleHeader[sid].pitchCorrection / 100 -
				(gen.overridingRootKey ?
					gen.overridingRootKey.amount :
					parser.sampleHeader[sid].originalPitch)
					sf2par.sampleHeader[sid].originalPitch)

			for (j = gen.keyRange.lo; j <= gen.keyRange.hi; j++) {
				rates[instr][j] = Math.pow(Math.pow(2, 1 / 12),
							(j + tune) * scale)
				params[instr][j] = parm
			}
		    }
		}
	} // sf2_create()

	// load an instrument (.js file)
	function load_instr(instr) {
		w_instr++
		abc2svg.loadjs(conf.sfu + '/' + instr + '.js',
			function() {
				parser = new sf2.Parser(b64dcod(abcsf2[instr]))
				parser.parse()
				presets = parser.getPresets()
				sf2_create(instr)
			    var	sf2par = new sf2.Parser(b64dcod(abcsf2[instr]))
				sf2par.parse()
			    var	sf2pre = sf2par.getPresets()
				sf2_create(instr, sf2par, sf2pre)

				if (--w_instr == 0)
					play_start()
			},
			function() {
				errmsg('could not find the instrument ' +
					((instr / 128) | 0).toString() + '-' +
					(instr % 128).toString())
				if (--w_instr == 0)
					play_start()
			})
	} // load_instr()

	// define the instruments of the tune
	function def_instr(s, f, sf2par, sf2pre) {
	    var	i,
		bk = [],		// bank number per voice
		nv = -1,		// highest voice number
		vb = 0			// bitmap of voices with instruments

		// scan from the beginning of the tune
		s = s.p_v.sym
		while (s.ts_prev)
			s = s.ts_prev

		for ( ; s; s = s.ts_next) {
			if (s.v < nv) {			// if new voice
				nv = s.v
				bk[nv] = 0		// bank 0
				if (p_v.midictl) {
					if (p_v.midictl[0])	// MSB
						bk[s.v] = (bk[s.v] & ~0x1fc000)
								+ (p_v.midictl[0] << 14)
					if (p_v.midictl[32])	// LSB
						bk[s.v] = (bk[s.v] & ~0x3f80)
								+ (p_v.midictl[32] << 7)
				}
			}
			switch (s.subtype) {
			case "midiprog":
				break
			case "midictl":
				if (s.ctrl != 0 && s.ctrl != 32)
					continue	// not bank LSB or MSB
				if (bk[s.v] == undefined)
					bk[s.v] = 0
				if (s.ctrl == 0)			// MSB
					bk[s.v] = (bk[s.v] & ~0x1fc000)
							+ (s.val << 14)
				else					// LSB
					bk[s.v] = (bk[s.v] & ~0x3f80)
							+ (s.val << 7)
//				continue
			default:
				continue
			}
			vb |= 1 << s.v
			i = s.instr
			if (i == undefined) {		// channel only
				if (s.chn != 9)
					continue
				i = bk[s.v] ? 0 : 128 * 128	// bank 256 program 0
			}
			if (bk[s.v]) 
				i += bk[s.v]		// bank number
			if (!params[i]) {
				params[i] = []		// instrument being loaded
				f(i, sf2par, sf2pre)	// sf2_create or load_instr
			}
		}
		nv = (2 << nv) - 1
		if (!(nv & vb)			// if some voice(s) without instrument
		 && !params[0]) {
			params[0] = []		// load the piano
			f(0, sf2par, sf2pre)
		}
	} // def_instr()

	// load the needed instruments
	function load_res(s) {
	    if (abc2svg.sf2
	     || conf.sfu.slice(-4) == ".sf2"
	     || conf.sfu.slice(-3) == ".js") {

		// if the soundfont is loaded as .js
		if (abc2svg.sf2) {
			if (!parser) {
				parser = new sf2.Parser(b64dcod(abc2svg.sf2))
				parser.parse()
				presets = parser.getPresets()
			}

		// load the soundfont if no done yet
		// load the soundfont if not done yet
		} else if (!parser) {
		    if (conf.sfu.slice(-3) == ".js") {
			abc2svg.loadjs(conf.sfu,
				function() {
					load_res(s)	// load the instruments
				},
				function() {
︙
335
336
337
338
339
340
341
342

343
344
345
346
347

348
349
350
351
352

353
354
355
356
357
358
359
360
361

362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377








378








379
















380
381
382
383
384
385
386
387

388

389

390
391

392
393
394
395
396
397
398
401
402
403
404
405
406
407

408





409





410
411
412
413






414





415
416
417
418
419
420
421
422
423
424

425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469

470
471

472
473
474
475
476
477
478
479







-
+
-
-
-
-
-
+
-
-
-
-
-
+



-
-
-
-
-
-
+
-
-
-
-
-










-
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








+

+
-
+

-
+







					errmsg('could not load the sound file '
						+ conf.sfu)
			}
			r.send()
			return
		}

		// create the instruments
		// create the instruments and start playing
		while (s) {
		    var	i = s.instr
			if (i != undefined && !params[i]) {
				params[i] = []	// instrument being loaded
				sf2_create(i)
		def_instr(s, sf2_create, parser, presets)
			}
			s = s.ts_next
		}
		play_start()
	   } else {
	    } else {

	// (case instruments as base64 encoded js file,
	//  one file per instrument)
		w_instr++			// play lock
		while (s) {
		    var	i = s.instr
			if (i != undefined && !params[i]) {
				params[i] = []	// instrument being loaded
				load_instr(i)
		def_instr(s, load_instr)
			}
			s = s.ts_next
		}
		if (--w_instr == 0)		// all resources were there already
			play_start()
	    }
	} // load_res()

	// return the play real time in seconds
	function get_time(po) {
		return po.ac.currentTime
	} // get_time()

	// MIDI control
	function midi_ctrl(po, s, t) {
		if (s.ctrl == 7)		// if volume
		switch (s.ctrl) {
		case 0:				// bank MSB
			if (po.v_b[s.v] == undefined)
				po.v_b[s.v] = 0
			po.v_b[s.v] = (po.v_b[s.v] & ~0x1fc000)
					+ (s.val << 14)
			break
		case 7:				// volume
			s.p_v.vol = s.val / 127
			break
		case 32:			// bank LSB
			if (po.v_b[s.v] == undefined)
				po.v_b[s.v] = 0
			po.v_b[s.v] = (po.v_b[s.v] & ~0x3f80)
					+ (s.val << 7)
			break
		}
	} // midi_ctrl()

	// MIDI prog or channel
	function midi_prog(po, s) {
	    var	i = s.instr

		po.v_c[s.v] = s.chn
		if (i == undefined) {
			if (s.chn != 9)			// if not channel 9
				return
			i = po.v_b[s.v] ? 0 : 128 * 128	// bank 256 program 0
		}
		if (po.v_b[s.v])
			i += po.v_b[s.v]
		po.c_i[s.chn] = i
//console.log('prog i:'+i+' ch:'+s.chn+' v:'+s.v)
	} // midi_prog()

	// create a note
	// @po = play object
	// @s = symbol
	// @key = MIDI key + detune
	// @t = audio start time
	// @d = duration adjusted for speed
	function note_run(po, s, key, t, d) {
//console.log('run c:'+po.v_c[s.v]+' i:'+po.c_i[po.v_c[s.v]])
	    var	g, st,
		c = po.v_c[s.v],
		instr = s.instr,
		instr = po.c_i[c],
		k = key | 0,
		parm = po.params[instr][k],
		parm = params[instr][k],
		o = po.ac.createBufferSource(),
		v = s.p_v.vol == undefined ? 1 : s.p_v.vol	// volume (gain)

		if (!v			// mute voice
		 || !parm)		// if the instrument could not be loaded
			return		// or if it has not this key
		o.buffer = parm.buffer
︙
431
432
433
434
435
436
437

438
439
440
441
442
443
444
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526







+







		// start the note
		o.start(t)
		o.stop(t + d)
	} // note_run()

	// wait for all resources, then start playing
	function play_start() {
//console.log('- play start')
		if (po.stop) {			// stop playing
			po.onend(repv)
			return
		}

		// all resources are there
		gain.connect(ac.destination)
︙
511
512
513
514
515
516
517

518
519



520
521
522
523
524
525
526

527


528
529
530
531
532
533
534
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609

610
611
612
613
614
615
616
617
618
619
620
621
622







+


+
+
+




-


+

+
+







			s_end: i_end,	// last music symbol / null
			s_cur: i_start,	// current music symbol
//			repn: false,	// don't repeat
			repv: i_lvl || 0, // repeat variant number
			tgen: 2,	// // generate by 2 seconds
			get_time: get_time,
			midi_ctrl: midi_ctrl,
			midi_prog: midi_prog,
			note_run: note_run,
			timouts: [],
			v_c: [],	// voice to channel
			c_i: [],	// channel to instrument
			v_b: [],	// voice to bank

			// audio specific
			ac: ac,
			gain: gain,
			params: params,
			rates: rates
		}
		w_instr++			// play lock
		load_res(i_start)
		if (--w_instr == 0)		// all resources are there
			play_start()
	}, // play()

	// stop playing
	stop: function() {
		po.stop = true
		po.timouts.forEach(function(id) {
					clearTimeout(id)
︙
Changes to util/sndgen.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// sndgen.js - sound generation
//
// Copyright (C) 2019-2021 Jean-Francois Moine
// Copyright (C) 2019-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

50
51
52
53

54
55

56
57
58
59
60
61
62
63
64
22
23
24
25
26
27
28


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

47
48
49
50

51
52

53


54
55
56
57
58
59
60







-
-


















-
+



-
+

-
+
-
-







//	s.ptim = play time
// - in BAR
//	rep_p = on a right repeat bar, pointer to the left repeat symbol
//	rep_s = on the first repeat variant, array of pointers to the next symbols,
//						indexed by the repeat number
// - in NOTE and REST
//	s.pdur = play duration
//	s.instr = bank + instrument
//	s.chn = MIDI channel
// - in the notes[] of NOTE
//	s.notes[i].midi

if (!abc2svg)
    var	abc2svg = {}

function ToAudio() {
 return {

   // generate the play data of a tune
   add: function(first,		// starting symbol
		voice_tb,	// voice table
		cfmt) {		// tune parameters
    var	toaud = this,
	C = abc2svg.C,
	p_time = 0,		// last playing time
	abc_time = 0,		// last ABC time
	play_fac = C.BLEN / 4 * 120 / 60, // play time factor - default: Q:1/4=120
	i, n, dt, d, v, c,
	i, n, dt, d, v,
	s = first,
	rst = s,		// left repeat (repeat restart)
	rst_fac,		// play factor on repeat restart
	rsk,			// repeat variant array (repeat skip)
	rsk = [],		// repeat variant array (repeat skip)
	b_tim,			// time of last measure bar
	b_typ,			// type of last measure bar
	b_typ			// type of last measure bar
	instr = [],		// [voice] bank + instrument
	chn = []		// [voice] MIDI channel

	// build the information about the parts (P:)
	function build_parts(first) {
	    var	i, j, c, n, v,
		s = first,
		p = s.parts,
		st = [],
︙
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215


216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247




248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265

266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
115
116
117
118
119
120
121







































































122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154


155
156
157
158
159
160
161
162
163
164
165
166
167
168
169



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

191













192

193
194
195
196



197
198
199
200
201
202
203







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-



















+
+












-
-















-
-
-
+
+
+
+

















-
+
-
-
-
-
-
-
-
-
-
-
-
-
-

-




-
-
-







					if (first.parts[i] == v)
						first.p_s[i] = s
				}
			}
		}
	} // build_parts()

	// set the starting MIDI instruments and channels
	function midi_start() {
	    var	v, p_v, c, i, ii

		for (v = 0; v < voice_tb.length; v++) {
			p_v = voice_tb[v]
			ii = p_v.instr || 0		// instrument
			c = p_v.chn			// channel
			if (c == undefined)
				c = p_v.v < 9 ? p_v.v : p_v.v + 1
			else if (c == 9)		// percussion
				ii = (ii & ~0x7f) | 16384

			if (p_v.midictl) {		// if MIDI controls
				for (i in p_v.midictl) {
					switch (Number(i)) {
					case 0:		// MSB bank
						ii = (ii & 0x3fff) |
							(p_v.midictl[i] << 14)
						break
					case 32:	// LSB bank
						ii = (ii & 0x1fc07f) |
							(p_v.midictl[i] << 7)
						break
					}
				}
			}

			if ((ii & ~0x7f) == 16384) // if bank 128 (percussion)
				c = 9			// channel '10'
			chn[v] = c
			instr[c] = ii
		}
	} // midi_start()

	// handle a block symbol
	function do_block(s) {
	    var	v = s.v,
		c = chn[v]

		switch (s.subtype) {
		case "midichn":
			chn[v] = s.chn
			break
		case "midictl":
			switch (s.ctrl) {
			case 0:			// MSB bank
				instr[c] = (instr[c] & 0x3fff) |
					(s.val << 14)
				break
			case 32:		// LSB bank
				instr[c] = (instr[c] & 0x1fc07f) |
					(s.val << 7)
				break
//			case 121:		// reset all controllers
//				instr = []
//				break
			}
			if ((instr[c] & ~0x7f) == 16384) { // if percussion
				instr[9] = instr[c]	// force the channel 10
				chn[v] = c = 9
			}
			s.chn = c
			break
		case "midiprog":
			instr[c] = (instr[c] & ~0x7f) | s.instr
			s.chn = c
			break
		}
	} // do_block()

	// generate the grace notes
	function gen_grace(s) {
	    var	g, i, n, t, d, s2,
		next = s.next

		// before beat
		if (s.sappo) {
			d = C.BLEN / 16
		} else if ((!next || next.type != C.NOTE)
			&& s.prev && s.prev.type == C.NOTE) {
			d = s.prev.dur / 2

		// on beat
		} else {
			d = next.dur / 12
			if (!(d & (d - 1)))
				d = next.dur / 2	// no dot
			else
				d = next.dur / 3
			if (s.p_v.key.k_bagpipe)
				d /= 2
			next.time += d
			next.dur -= d
		}
//fixme: assume the grace notes in the sequence have the same duration
		n = 0
		for (g = s.extra; g; g = g.next)
			n++
		d /= n * play_fac
		t = p_time
		for (g = s.extra; g; g = g.next) {
			g.ptim = t
			g.pdur = d
			g.chn = chn[s.v]
			g.instr = instr[g.chn]
			t += d
		}
	} // gen_grace()

	// change the tempo
	function set_tempo(s) {
	    var	i,
		d = 0,
		n = s.tempo_notes.length

		for (i = 0; i < n; i++)
			d += s.tempo_notes[i]
		return d * s.tempo / 60
	} // set_tempo()

	function set_variant(rsk, n, s) {
	    var	d
		n = n.match(/[1-8]-[2-9]|[1-9,.]|[^\s]+$/g)
	function set_variant(s) {
	    var	d,
		n = s.text.match(/[1-8]-[2-9]|[1-9,.]|[^\s]+$/g)

		while (1) {
			d = n.shift()
			if (!d)
				break
			if (d[1] == '-')
				for (i = d[0]; i <= d[2]; i++)
					rsk[i] = s
			else if (d >= '1' && d <= '9')
				rsk[Number(d)] = s
			else if (d != ',')
				rsk.push(s)	// last
		}
	} // set_variant()

	// add() main

	// if some MIDI stuff, load the associated module
	if (cfmt.chord) {
	if (cfmt.chord)
		if (!abc2svg.chord) {
			abc2svg.pwait = true	// don't start playing now

			abc2svg.loadjs("chord-1.js",
					function(){	// ok
						toaud.add(first, voice_tb, cfmt)
					},
					function(){	// KO
						cfmt.chord = null
						toaud.add(first, voice_tb, cfmt)
					})
			return
		}
		abc2svg.chord(first, voice_tb, cfmt)
	}

	if (s.parts)
		build_parts(s)

	// get the starting MIDI parameters
	midi_start()

	// set the time parameters
	rst_fac = play_fac
	while (s) {
		if (s.noplay) {			// in display macro sequence
			s = s.ts_next
			continue
		}
︙
306
307
308
309
310
311
312
313


314
315
316
317
318

319
320
321
322
323
324
325
326
327
328
329

330
331
332
333
334
335
336
337
338
339
340
341
342
343
344

345
346
347
348
349

350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
215
216
217
218
219
220
221

222
223
224
225
226
227

228
229
230
231
232
233
234
235
236
237
238

239
240
241
242
243
244
245
246
247
248
249
250
251
252
253

254
255
256
257
258

259
260
261
262
263
264
265
266
267
268
269
270
271
272



273
274
275
276
277
278
279







-
+
+




-
+










-
+














-
+




-
+













-
-
-







		}
		switch (s.type) {
		case C.BAR:
			if (s.time != b_tim) {
				b_tim = s.time
				b_typ = 0
			}
			if (s.text && rsk		// if new variant
			if (s.text			// if new variant
			 && rsk.length > 1
			 && s.text[0] != '1') {
				if (b_typ & 1)
					break
				b_typ |= 1
				set_variant(rsk, s.text, s)
				set_variant(s)
				play_fac = rst_fac
				rst = rsk[0]		// reinit the restart
			}

			// right repeat
			if (s.bar_type[0] == ':') {
				if (b_typ & 2)
					break
				b_typ |= 2
				s.rep_p = rst		// :| to |:
				if (rsk && rst == rsk[0])
				if (rst == rsk[0])
					s.rep_v = rsk	// to know the number of variants
			}

			// 1st time repeat
			if (s.text) {
			    if (s.text[0] == '1') {
				if (b_typ & 1)
					break
				b_typ |= 1
				s.rep_s = rsk = [rst]	// repeat skip
							// and memorize the restart
				if (rst.bar_type
				 && rst.bar_type.slice(-1) != ':')
					rst.bar_type += ':' // restart confirmed
				set_variant(rsk, s.text, s)
				set_variant(s)
				rst_fac = play_fac
			    }

			// left repeat
			} else if (s.rbstop) {
			} else if (s.rbstop == 2) {
				if (s.bar_type.slice(-1) == ':') {
					if (b_typ & 4)
						break
					b_typ |= 4
				} else {
					if (b_typ & 8)
						break
					b_typ |= 8
				}
				rst = s			// new possible restart
				rst_fac = play_fac
			}
			break
		case C.BLOCK:
			do_block(s)
			break
		case C.GRACE:
			if (s.time == 0		// if before beat at start time
			 && abc_time == 0) {
				dt = 0
				if (s.sappo)
					dt = C.BLEN / 16
				else if (!s.next || s.next.type != C.NOTE)
︙
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
293
294
295
296
297
298
299



300
301
302
303
304
305
306
307








308
309
310
311
312
313
314







-
-
-








-
-
-
-
-
-
-
-







					dt = d / 2
				s.next.time -= dt
				d -= dt
			}
			d /= play_fac
			s.pdur = d
			v = s.v
			c = chn[v]			// channel
			s.chn = c
			s.instr = instr[c]
			break
		case C.TEMPO:
			if (s.tempo)
				play_fac = set_tempo(s)
			break
		}
		s = s.ts_next
	} // loop

	// if playing was waiting for a resource, start it now
	if (abc2svg.pwait) {
		i = abc2svg.pwait
		delete abc2svg.pwait
		if (typeof i == "function")
			i()
	}
   } // add()
 } // return
} // ToAudio()

// play some next symbols
//
// This function is called to start playing.
︙
428
429
430
431
432
433
434
435

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452

453
454

455
456
457
458
459
460
461
462
324
325
326
327
328
329
330

331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347

348
349

350

351
352
353
354
355
356
357







-
+
















-
+

-
+
-







//  - s_cur: current symbol (next to play)
//		must be set to the first symbol to be played at startup time
//  - s_end: stop playing on this symbol
//		this symbol is not played. It may be null.
//  - conf
//    - speed: current speed factor
//		must be set to 1 at startup time
//    - new_conf: new speed factor
//    - new_speed: new speed factor
//		set by the user
// - internal variables
//  - stim: start time
//  - repn: don't repeat
//  - repv: variant number
//  - timouts: array of the current timeouts
//		this array may be used by the upper function in case of hard stop
//  - p_v: voice table used for MIDI control
// - methods
//  - onend: (optional)
//  - onnote: (optional)
//  - note_run: start playing a note
//  - get_time: return the time of the underlaying sound system
abc2svg.play_next = function(po) {

	// handle a tie
	function do_tie(note, d) {
	function do_tie(not_s, d) {
	    var	i,
		s = note.s,
		s = not_s.s,
		midi = note.midi,
		C = abc2svg.C,
		v = s.v,
		end_time = s.time + s.dur,
		repv = po.repv

		// search the end of the tie
		while (1) {
︙
474
475
476
477
478
479
480

481
482


483
484
485
486
487
488

489
490
491

492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516

517
518


519








520
521
522
523
524
525
526
527























528
529
530
531
532
533
534
369
370
371
372
373
374
375
376
377

378
379
380
381
382
383
384

385

386

387
388
389
390
391
392
393
394
395
396
397
398

399
400
401
402
403
404
405
406
407
408
409
410

411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462







+

-
+
+





-
+
-

-
+











-












-
+


+
+

+
+
+
+
+
+
+
+








+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







					if (!s.rep_s[repv])
						break
					s = s.rep_s[repv++]
					end_time = s.time
				}
				while (s.ts_next && !s.ts_next.dur)
					s = s.ts_next
				continue
			}
			if (s.type != C.NOTE)
			if (s.time < end_time
			 || !s.ti2)			// if not end of tie
				continue

			i = s.notes.length
			while (--i >= 0) {
				note = s.notes[i]
				if (note.midi == midi) {
				if (note.tie_s == not_s) {
					note.ti2 = true	// the sound is generated
					d += s.pdur / po.conf.speed
					return note.tie_ty ? do_tie(note, d) : d
					return note.tie_e ? do_tie(note, d) : d
				}
			}
		}

		return d
	} // do_tie()

	// set the MIDI controls up to now
	function set_ctrl(po, s2, t) {
	    var	i,
		p_v = s2.p_v,
		tim = s2.time,
		s = {
			subtype: "midictl",
			p_v: p_v,
			v: p_v.v,
			chn: p_v.chn
		}

		for (i in p_v.midictl) { // MIDI controls at voice start time
			s.ctrl = Number(i)
			s.val = p_v.midictl[i]
			po.midi_ctrl(po, s, t)
		}
		for (s = p_v.sym; s && s.time <= tim; s = s.next) {
		for (s = p_v.sym; s != s2; s = s.next) {
			if (s.subtype == "midictl")
				po.midi_ctrl(po, s, t)
			else if (s.subtype == 'midiprog')
				po.midi_prog(po, s)
		}

		// if no %%MIDI, set 'grand acoustic piano' as the instrument
		i = po.v_c[s2.v]
		if (i == undefined)
			po.v_c[s2.v] = i = s2.v < 9 ? s2.v : s2.v + 1
		if (po.c_i[i] == undefined)
			po.c_i[i] = 0	// piano

		po.p_v[s2.v] = true	// synchronization done
	} // set_ctrl()

    // start and continue to play
    function play_cont(po) {
    var	d, i, st, m, note, g, s2, t, maxt, now,
	C = abc2svg.C,
	s = po.s_cur

	// search the end of a sequence of variants
	function var_end(s) {
	    var	i, s2, s3,
		a = s.rep_v || s.rep_s
		ti = 0

		for (i = 1; i < a.length; i++) {
			s2 = a[i]
			if (s2.time > ti) {
				ti = s2.time
				s3 = s2
			}
		}
		for (s = s3; s != po.s_end; s = s.ts_next) {
			if (s.time == ti)
				continue
			if (s.rbstop == 2)
				break
		}
		po.repv = 1		// repeat end
		return s
	} // var_end()

	if (po.stop) {
		if (po.onend)
			po.onend(po.repv)
		return
	}

︙
555
556
557
558
559
560
561

562
563
564
565
566

567
568
569
570
571
572





573
574

575
576
577
578
579
580
581
582
583
584
585
586


587
588
589




590
591
592


593











594
595
596
597
598
599
600
483
484
485
486
487
488
489
490
491
492
493
494

495






496
497
498
499
500


501

502

503
504
505
506




507
508
509



510
511
512
513
514
515

516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536







+




-
+
-
-
-
-
-
-
+
+
+
+
+
-
-
+
-

-




-
-
-
-

+
+
-
-
-
+
+
+
+


-
+
+

+
+
+
+
+
+
+
+
+
+
+







	maxt = t + po.tgen		// max time = now + 'tgen' seconds
	po.timouts = []
	while (1) {
		if (!po.p_v[s.v])		// if new voice
			set_ctrl(po, s, t)	// set the MIDI controls
		switch (s.type) {
		case C.BAR:
			s2 = null
			if (s.rep_p) {		// right repeat
				po.repv++
				if (!po.repn	// if repeat a first time
				 && (!s.rep_v	// and no variant (anymore)
				  || po.repv < s.rep_v.length)) {
				  || po.repv <= s.rep_v.length)) {
					po.stim += (s.ptim - s.rep_p.ptim) /
							po.conf.speed
					s = s.rep_p	// left repeat
					while (s.ts_next
					 && !s.ts_next.dur)
						s = s.ts_next
					s2 = s.rep_p	// left repeat
					po.repn = true
				} else {
					if (s.rep_v)
						s2 = var_end(s)
					t = po.stim + s.ptim / po.conf.speed
					po.repn = true
					po.repn = false
					break
				}
				po.repn = false
			}
			if (s.rep_s) {			// first variant
				s2 = s.rep_s[po.repv]	// next variant
				if (s2) {
					po.stim += (s.ptim - s2.ptim) /
							po.conf.speed
					s = s2
					t = po.stim + s.ptim / po.conf.speed
					po.repn = false
					if (s2 == s)
						s2 = null
				} else {		// end of tune
					s = po.s_end
					break
				} else {		// end of variants
					s2 = var_end(s)
					if (s2 == po.s_end)
						break
				}
			}
			if (s.bar_type.slice(-1) == ':') // left repeat
			if (s.bar_type.slice(-1) == ':' // left repeat
			 && s.bar_type[0] != ':')	// but not ::
				po.repv = 1

			if (s2) {			// if skip
				po.stim += (s.ptim - s2.ptim) / po.conf.speed
				s = s2
				while (s && !s.dur)
					s = s.ts_next
				if (!s)
					break		// no ending variant
				t = po.stim + s.ptim / po.conf.speed
				break
			}

		    if (!s.part1) {
			while (s.ts_next && !s.ts_next.seqst) {
				s = s.ts_next
				if (s.part1)
					break
			}
︙
620
621
622
623
624
625
626


627
628
629
630
631
632

633

634
635
636
637
638
639
640
641
642
643
644
645
646
647
648



649
650
651
652

653
654
655
656
657
658
659
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571

572
573
574
575
576
577
578
579
580
581
582
583
584
585


586
587
588
589
590
591

592
593
594
595
596
597
598
599







+
+






+
-
+













-
-
+
+
+



-
+







	    if (s && s != po.s_end) {
		switch (s.type) {
		case C.BAR:
			break
		case C.BLOCK:
			if (s.subtype == "midictl")
				po.midi_ctrl(po, s, t)
			else if (s.subtype == 'midiprog')
				po.midi_prog(po, s)
			break
		case C.GRACE:
			for (g = s.extra; g; g = g.next) {
				d = g.pdur / po.conf.speed
				for (m = 0; m <= g.nhd; m++) {
					note = g.notes[m]
					if (!note.noplay)
					po.note_run(po, g,
					    po.note_run(po, g,
						note.midi,
						t + g.ptim - s.ptim,
//fixme: there may be a tie...
						d)
				}
			}
			break
		case C.NOTE:
		case C.REST:
			d = s.pdur / po.conf.speed
		    if (s.type == C.NOTE) {
			for (m = 0; m <= s.nhd; m++) {
				note = s.notes[m]
				if (note.ti2)
					continue
				if (note.tie_s		// end of tie
				 || note.noplay)	// (%%voicecombine)
					continue	// already generated
				po.note_run(po, s,
					note.midi,
					t,
					note.tie_ty ?
					note.tie_e ?
						do_tie(note, d) : d)
			}
		    }

			// follow the notes/rests while playing
			if (po.onnote && s.istart) {
				i = s.istart
︙
Changes to util/sndmem.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// sndmem.js - generate play events for use with toaudio5 or tomidi5
//
// Copyright (C) 2020-2021 Jean-Francois Moine
// Copyright (C) 2020-2022 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
40
41
42
43
44
45
46
47

48
49
50
51
52
53
54
40
41
42
43
44
45
46

47
48
49
50
51
52
53
54







-
+







	// @k = MIDI key + detune
	// @t = audio start time (ms)
	// @d = duration adjusted for speed (ms)
	function note_run(po, s, k, t, d) {
		po.a_e.push(new Float32Array([
				s.istart,
				t,
				s.instr,
				po.c_i[po.v_c[s.v]],
				k,
				d,
				1,
				s.v]))
	} // note_run()

	// return the play real time in seconds
︙
63
64
65
66
67
68
69







70
71
72
73
74
75
76
77
78

79


80
81
82
83
84
85
86
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96







+
+
+
+
+
+
+









+

+
+







				0,	// (time - unused)
				-1,	// MIDI control
				s.ctrl,
				s.val,
				1,
				s.v]))
	} // midi_ctrl()

	// MIDI channel and/or program
	function midi_prog(po, s) {
		po.v_c[s.v] = s.chn
		if (s.instr)
			po.c_i[s.chn] = s.instr
	} // midi_prog()

	// define the play object
	po = {
		conf: {		// configuration
			speed: 1
		},
		tgen: 3600, 	// generate by (for) 1 hour
		get_time: get_time,
		midi_ctrl: midi_ctrl,
		midi_prog: midi_prog,
		note_run: note_run,
		v_c: [],	// voice to channel
		c_i: [],	// channel to instrument

		// sndmem specific
		a_e: []
	}

	// loop on the tunes
	while (1) {
︙
Changes to util/sndmid.js.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







// sndmid.js - audio output using HTML5 MIDI
//
// Copyright (C) 2019-2021 Jean-Francois Moine
// Copyright (C) 2019-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
︙
62
63
64
65
66
67
68
69
70


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
62
63
64
65
66
67
68


69
70
71
72
73
74
75
76

















77
78
79
80
81
82
83







-
-
+
+






-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







	// @s = symbol
	// @k = MIDI key + detune
	// @t = audio start time (s)
	// @d = duration adjusted for speed (s)
	function note_run(po, s, k, t, d) {
	    var	j,
		a = (k * 100) % 100,	// detune in cents
		i = s.instr,
		c = s.chn
		c = po.v_c[s.v],
		i = po.c_i[c]

		k |= 0			// remove the detune value

		t *= 1000		// convert to ms
		d *= 1000		

		if (i != po.c_i[c]) {		// if program change

			// at channel start, reset and initialize the controllers
			if (po.c_i[c] == undefined) {
//fixme: does not work with fluidsynth
				po.op.send(new Uint8Array([0xb0 + c, 121, 0]))
				if (s.p_v.midictl) {
				    for (j in s.p_v.midictl)
					po.op.send(new Uint8Array([0xb0 + c,
								j,
								s.p_v.midictl[j]]))
				}
			}

			po.c_i[c] = i
			po.op.send(new Uint8Array([0xc0 + c, i & 0x7f])) // program
		}
		if (a && Midi5.ma.sysexEnabled) {	// if microtone
// fixme: should cache the current microtone values
			po.op.send(new Uint8Array([
				0xf0, 0x7f,	// realtime SysEx
				0x7f,		// all devices
				0x08,		// MIDI tuning standard
				0x02,		// note change
︙
113
114
115
116
117
118
119


























120
121
122
123
124
125
126
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








	// send a MIDI control
	function midi_ctrl(po, s, t) {
		po.op.send(new Uint8Array([0xb0 + s.chn,
					s.ctrl, s.val]),
			t * 1000)
	} // midi_ctrl()

	// change the channel and/or send a MIDI program	
	function midi_prog(po, s) {
	    var	i,
		c = s.chn

		po.v_c[s.v] = c
		if (s.instr != undefined)
			return
		i = po.c_i[c]

		// at channel start, reset and initialize the controllers
		if (i == undefined) {
//fixme: does not work with fluidsynth
			po.op.send(new Uint8Array([0xb0 + c, 121, 0]))
			if (s.p_v.midictl) {
			    for (i in s.p_v.midictl)
				po.op.send(new Uint8Array([0xb0 + c,
							i,
							s.p_v.midictl[i]]))
			}
		}

		po.c_i[c] = i = s.instr
		po.op.send(new Uint8Array([0xc0 + c, i & 0x7f])) // program
	} // midi_prog()

	// MIDI output is possible,
	// return the possible ports in return to get_outputs()
	function send_outputs(access) {
	    var	o, os,
		out = []

︙
192
193
194
195
196
197
198

199
200
201
202
203

204
205
206
207
208
209
210
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221







+





+







				s_end: i_end,	// last music symbol / null
				s_cur: i_start,	// current music symbol
//				repn: false,	// don't repeat
				repv: i_lvl || 0, // repeat variant number
				tgen: 2, 	// generate by 2 seconds
				get_time: get_time,
				midi_ctrl: midi_ctrl,
				midi_prog: midi_prog,
				note_run: note_run,
				timouts: [],

				// MIDI specific
				op: op,		// output port
				v_c: [],	// voice to channel
				c_i: []		// channel to instrument
			}
if (0) {
// temperament
			op.send(new Uint8Array([
				0xf0, 0x7f,	// realtime SysEx
				0x7f,		// all devices
︙