Index: README.md
==================================================================
--- README.md
+++ README.md
@@ -156,14 +156,14 @@
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
+of the URL of the page. The [following example][9] 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"
+[9]: 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 offers a tune selection.
@@ -347,10 +347,16 @@
` abcqjs toodt.js my_file.abc -o my_file.odt`
- `toparam.js`
This script just outputs the abc2svg parameters.
+- `tosvg.js`
+ This script creates a SVG file that includes all the SVGs of the generation.
+
+ The included SVG images are updated for being correctly rendered by tools
+ that use librsvg, the SVG library for GNOME (feh, viewnior, swayimg...).
+
#### 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'),
@@ -499,12 +505,11 @@
[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
+ SVG images of the music. The music source is displayed
in a <pre> element of class `source`. The SVG's are included
in a <div> 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}`).
@@ -513,11 +518,11 @@
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 <script> type "text/vnd.abc", class="abc" or inlined ABC)
+ (in <script> type "text/vnd.abc", class="abc" or inlined ABC).
Its argument is the HTML <div> element that contains the music.
- `abc2svg.set_music` has two arguments, the HTML <div> element
and the new source of the music.
It generates and replaces the music in the <div>.
Index: abc2svg.ttf
==================================================================
--- abc2svg.ttf
+++ abc2svg.ttf
cannot compute difference between binary files
Index: abcqjs
==================================================================
--- abcqjs
+++ abcqjs
@@ -1,6 +1,6 @@
-:;# shell script to convert ABC or MEI music notation to other format
+# shell script to convert ABC or MEI music notation to other format
# using abc2svg and quickjs (https://bellard.org/quickjs/)
# search where are the abc2svg scripts
p=$0
if [ `dirname $p` = . ]; then
@@ -68,10 +68,14 @@
load(path + core)
load(path + "cmdline.js")
abc_cmd("abcqjs", args, "QuickJS")
+
+// test: list the global variables
+//for (var k in this)
+// console.log(k)
' $p "$@"
The previous solution with the shebang line as
#!/usr/bin/env -S qjs --std
did not work with non-GNU versions of env
Index: abctopdf
==================================================================
--- abctopdf
+++ abctopdf
@@ -85,15 +85,15 @@
shift
done
# check if some chromium-based browser is available
for c in "$BROWSER" chromium-browser vivaldi opera brave-browser epic google-chrome; do
- if command -v "$c" >/dev/null 2>&1; then
+ if command -v $c >/dev/null 2>&1; then
echo "and $c"
command $abcscr "$@" > /tmp/o.html
-# --headless=new does not work
- "$c" --headless --print-to-pdf=$out --no-pdf-header-footer\
+# --headless does not work anymore (since vivaldi 6.9 2024-09-10)
+ $c --headless=old --print-to-pdf=$out --no-pdf-header-footer\
/tmp/o.html
rm /tmp/o.html
exit 0
fi
done
Index: build
==================================================================
--- build
+++ build
@@ -23,11 +23,11 @@
cat "$@"
}
fi
out=abc2svg-$MAJOR.js
-VERSION='??????'
+VERSION=`sed -e 's/^[^"]*"//' -e's/".*//' version.txt`
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\
Index: build.ninja
==================================================================
--- build.ninja
+++ build.ninja
@@ -24,13 +24,14 @@
command = if [ -e .fslckout ] && command -v fossil >/dev/null 2>&1; then $
v=`fossil describe`;$
d=`date -Idate`;$
v="abc2svg.version=\"$$v\";abc2svg.vdate=\"$$d\"";$
else $
- v=`grep abc2svg.version $out`;$
+ v=`cat version.txt`;$
fi;$
cp core/license $out;$
+ head -qn1 $in >> $out;$
if [ "x$$NOMIN" = "x" ]; then $
if command -v jsmin >/dev/null 2>&1; then $
cat $in | jsmin >> $out;$
echo "" >> $out;$
else $
@@ -45,10 +46,11 @@
fi;$
echo $$v >> $out
rule jscu
command = cp core/license $out;$
+ head -qn1 $in >> $out;$
if [ "x$$NOMIN" = "x" ]; then $
if command -v jsmin >/dev/null 2>&1; then $
cat $in | jsmin >> $out;$
echo "" >> $out;$
else $
@@ -92,11 +94,10 @@
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
build ambitus-$MAJOR.js: jscu modules/ambitus.js
build break-$MAJOR.js: jscu modules/break.js
build capo-$MAJOR.js: jscu modules/capo.js
build chordnames-$MAJOR.js: jscu modules/chordnames.js
@@ -121,10 +122,11 @@
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 tempo-$MAJOR.js: jscu modules/tempo.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
@@ -147,12 +149,12 @@
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 psvg-$MAJOR.js $
roman-$MAJOR.js soloffs-$MAJOR.js sth-$MAJOR.js strtab-$MAJOR.js $
- temper-$MAJOR.js tropt-$MAJOR.js edit-$MAJOR.css edit-$MAJOR.js edit-$MAJOR.xhtml $
- abckbd-$MAJOR.js abckbd2-$MAJOR.js
+ temper-$MAJOR.js tempo-$MAJOR.js tropt-$MAJOR.js edit-$MAJOR.css edit-$MAJOR.js $
+ edit-$MAJOR.xhtml abckbd-$MAJOR.js abckbd2-$MAJOR.js
default all
# fossil new release
rule version
@@ -165,19 +167,20 @@
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;$
+ d=`date -Idate`;$
+ echo "abc2svg.version=\"$$v\";abc2svg.vdate=\"$$d\"" > version.txt;$
+ fossil commit -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;$
+ cat version.txt >> 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
Index: cmdline.js
==================================================================
--- cmdline.js
+++ cmdline.js
@@ -94,10 +94,15 @@
var arg, parm, fn;
abc2svg.abort = function(e) {
abc2svg.printErr('javascript error: ' + e.message +
'\nStack:\n' + e.stack)
+ if (abc) {
+ abc.parse.state = 0 // force block flush
+ abc.blk_flush()
+ abc2svg.abc_end()
+ }
abc2svg.quit()
} // abort()
// put the last options before the last file
function arg_reorder(a) {
Index: core/abc2svg.js
==================================================================
--- core/abc2svg.js
+++ core/abc2svg.js
@@ -1,8 +1,8 @@
// abc2svg - abc2svg.js
//
-// Copyright (C) 2014-2022 Jean-Francois Moine
+// Copyright (C) 2014-2024 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
@@ -169,10 +169,13 @@
} // ch_alias
// global fonts
abc2svg.font_tb = [] // fonts - index = font.fid
abc2svg.font_st = {} // font style => font_tb index for incomplete user fonts
+
+// cache for converting a duration into [head, dots, nflags]
+abc2svg.hdn = {}
// font weight
// reference:
// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
abc2svg.ft_w = {
@@ -354,11 +357,11 @@
}
function error(sev, s, msg, a1, a2, a3, a4) {
var i, j, regex, tmp
- if (!sev && cfmt.quiet)
+ if (sev < cfmt.quiet)
return
if (s) {
if (s.err) // only one error message per symbol
return
s.err = true
Index: core/deco.js
==================================================================
--- core/deco.js
+++ core/deco.js
@@ -55,20 +55,20 @@
a_de, // array of the decoration elements
cross // cross voice decorations
// decorations - populate with standard decorations
var decos = {
- dot: "0 stc 6 1.5 1",
+ dot: "0 stc 6 .7 1",
tenuto: "0 emb 6 4 3",
- slide: "1 sld 3 7 1",
+ slide: "1 sld 10 7 1",
arpeggio: "2 arp 12 10 3",
roll: "3 roll 5,4 5 6",
lowermordent: "3 lmrd 6,5 4 6",
uppermordent: "3 umrd 6,5 4 6",
trill: "3 trl 14 5 8",
- upbow: "3 upb 10,2 3 7",
- downbow: "3 dnb 9 4 6",
+ upbow: "3 upb 12,2 3 7",
+ downbow: "3 dnb 8,2 4 6",
gmark: "3 grm 7 4 6",
wedge: "0 wedge 8 1.5 1", // (staccatissimo or spiccato)
longphrase: "5 lphr 0 1 16",
mediumphrase: "5 mphr 0 1 16",
shortphrase: "5 sphr 0 1 16",
@@ -173,17 +173,18 @@
courtesy: "43 0 0 0 0",
"cacc-1": "3 cacc-1 0 0 0",
cacc3: "3 cacc3 0 0 0",
cacc1: "3 cacc1 0 0 0",
"tie(": "44 0 0 0 0",
- "tie)": "44 0 0 0 0"},
+ "tie)": "44 0 0 0 0",
+ fg: "45 0 0 0 0"},
// types of decoration per function
f_near = [
d_near, // 0 - near the note
- d_slide, // 1
- d_arp // 2
+ d_slide, // 1 - slide or tied to the note stem
+ d_arp // 2 - arpeggio
],
f_note = [
null, null, null,
d_upstaff, // 3 - tied to note
d_upstaff // 4 (below the staff)
@@ -263,10 +264,13 @@
case C.SL_ABOVE:
return 1 // true
case C.SL_BELOW:
return 0 // false
}
+ if (!s.multi // if voice alone in the staff
+ && !s.invis) // but not invisible symbol of a secondary voice
+ return 1 //true
// if (s.multi)
// return s.multi > 0
// return 1 // true
return !s.second
} // up3()
@@ -338,12 +342,20 @@
y = up ? s.ymx : s.ymn
if (y > 0 && y < 24) {
y = (((y + 9) / 6) | 0) * 6 - 6 // between lines
}
if (up) {
- y += dd.hd
- s.ymx = y + dd.h
+ if (s.ys > 27
+ && dd.name[0] == 'd' // if dot (staccato)
+ && s.a_dd[0].name == "dot" // as the first decoration
+ && s.stem > 0 && s.nflags >= 0
+ && s.beam_st && s.beam_end)
+ y -= 6 // put the dot a bit lower
+ else
+ y += dd.hd
+ if (s.ymx < y + dd.h)
+ s.ymx = y + dd.h
} else if (dd.name[0] == 'w') { // wedge (no descent)
de.inv = true
y -= dd.h
s.ymn = y
} else {
@@ -352,38 +364,19 @@
}
de.x -= dd.wl
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 (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
- }
- }
- }
- }
}
/* 1: special case for slide */
function d_slide(de) {
- var m, dx,
- s = de.s,
- yc = s.notes[0].pit,
- xc = 5
+ var m, dx, xc,
+ s = de.s,
+ yc = s.y
+ if (de.dd.glyph == "sld") { // !slide!
for (m = 0; m <= s.nhd; m++) {
if (s.notes[m].acc) {
dx = 4 + s.notes[m].shac
} else {
dx = 5 - s.notes[m].shhd
@@ -395,16 +388,46 @@
case C.OVAL:
dx += 2
break
}
}
- if (s.notes[m].pit <= yc + 3 && dx > xc)
- xc = dx
+ if (s.notes[m].pit <= yc + 3 && dx > 5)
+ xc = -dx
+ else
+ xc = -5
}
-// de.x = s.x - xc;
- de.x -= xc;
- de.y = 3 * (yc - 18)
+ } else { // decoration tied to the stem
+ if (de.s.stem >= 0) {
+ if (s.nflags >= -1) {
+ xc = 3.5
+ yc = s.ys
+ if (s.nflags > 1)
+ yc -= 4 * (s.nflags - 1)
+ } else {
+ xc = 0
+ yc = s.y + 21
+ }
+ yc = (yc + 3 * (s.notes[s.nhd].pit - 18)) / 2
+ } else {
+ de.rotpi = 1//true // rotate pi (180°)
+ if (s.nflags >= -1) {
+ xc = -3.5
+ yc = s.ys
+ if (s.nflags > 1)
+ yc += 4 * (s.nflags - 1)
+ } else {
+ xc = 0
+ yc = s.y - 21
+ }
+ yc = (yc + 3 * (s.notes[0].pit - 18)) / 2
+ }
+ }
+ de.x += xc;
+ de.y = yc
+
+ if (de.y < 0)
+ y_set(s.st, 0, de.x, de.dd.wl, de.y - de.dd.h)
}
// special case for long decoration
function d_trill(de) {
if (de.ldst)
@@ -560,21 +583,22 @@
x = de.x,
w = dd.wl + dd.wr
// glyphs inside the staff
switch (dd.glyph) {
- case "brth":
- case "caes":
case "lphr":
case "mphr":
case "sphr":
case "short":
case "tick":
- y = staff_tb[s.st].topbar + 2 + dd.hd
- if (s.type == C.BAR) {
+ if (s.type == C.BAR)
s.invis = 1
- } else {
+ // fall thru
+ case "brth":
+ case "caes":
+ y = staff_tb[s.st].topbar + 2 + dd.hd
+ if (!s.invis) {
if (dd.glyph == "brth" && y < s.ymx)
y = s.ymx
for (s = s.ts_next; s; s = s.ts_next)
if (s.seqst)
break
@@ -676,11 +700,11 @@
if (isNaN(c_func)) {
error(1, null, "%%deco: bad C function value '$1'", a[1])
return //undefined
}
if (c_func > 10
- && (c_func < 32 || c_func > 44)) {
+ && (c_func < 32 || c_func > 45)) {
error(1, null, "%%deco: bad C function index '$1'", c_func)
return //undefined
}
// if (c_func == 5) // old !trill(!
// c_func = 3
@@ -766,10 +790,13 @@
if (nt2) {
error(1, s, "Conflict on !$1!", nm)
return
}
+ if (nt1.tie_ty) // if normal '-'
+ curvoice.tie_s = null
+
nt1.s = s
nt2 = cross[nm2]
if (!nt2) {
cross[nm] = nt1 // keep the start/end
return
@@ -778,14 +805,15 @@
nt2 = nt1
nt1 = cross[nm2]
}
cross[nm2] = null
if (nt1.midi != nt2.midi
- || nt1.s.time + nt1.s.dur != nt2.s.time) {
+ || nt1.s.time + nt1.dur != nt2.s.time) {
error(1, s, "Bad tie")
} else {
- nt1.tie_ty = C.SL_AUTO
+ if (!nt1.tie_ty) // if not normal '-'
+ nt1.tie_ty = C.SL_AUTO
nt1.tie_e = nt2
nt2.tie_s = nt1
nt1.s.ti1 = nt2.s.ti2 = true
}
} // do_ctie()
@@ -824,11 +852,44 @@
return dd
} // get_dd()
/* -- convert the decorations -- */
function deco_cnv(s, prev) {
- var i, j, dd, nm, note, s1, court
+ var i, j, dd, nm, note, s1, court, fg
+
+ // mark a finger glissando
+ function sav_fg() {
+ var i,
+ s1 = prev
+
+ if (s.type != C.NOTE)
+ return 1
+ while (s1 && s1.type != C.NOTE)
+ s1 = s1.prev
+ if (!s1)
+ return 1
+ for (i = 0; i < s1.a_dd.length; i++) {
+ if (s1.a_dd[i].name == dd.name) {
+ if (!s.fg)
+ s.fg = []
+ s.fg.push({
+ ty: 1, // end of glissando
+ s: s1,
+ nm: dd.name
+ })
+ if (!s1.fg)
+ s1.fg = []
+ s1.fg.push({
+ ty: 0, // start of glissando
+ s: s,
+ nm: dd.name
+ })
+ return 0
+ }
+ }
+ return 1
+ } // sav_fg()
while (1) {
nm = a_dcn.shift()
if (!nm)
break
@@ -842,16 +903,28 @@
if (s.type == C.BAR && nm == "dot") {
s.bar_dotted = true
continue
}
// fall thru
- case 1: // slide
+ case 1: // slide & deco on stem
+ if (nm != "slide")
+ s.decstm=dd.h // deco stem
+ // fall thru
case 2: // arp
// if (s.type != C.NOTE && s.type != C.REST) {
if (!s.notes) {
error(1, s, errs.must_note_rest, nm)
continue
+ }
+ break
+ case 3:
+ if (fg && dd.glyph == "fng") { // move the fingers out of staves
+ for (i = 0; i <= 5; i++) {
+ decos[i.toString()] = "5 fng 5,5 3 3 " + i
+ if (dd_tb[i.toString()])
+ dd_tb[i.toString()].func = 5
+ }
}
break
case 4: // below the staff
case 5: // above the staff
i = nm.match(/1?[85]([vm])([ab])([()])/)
@@ -1004,13 +1077,27 @@
error(1, s, errs.must_note, nm)
continue
}
do_ctie(nm, s, s.notes[0]) // (only one note for now)
continue
+ case 45: // finger glissando
+ fg = 1 //true
+ continue
// default:
// break
}
+
+ // handle the fingering in case finger glissando
+ if (fg && dd.glyph == 'fng') {
+ fg = 0 //false
+ if (sav_fg()) {
+ error(1, s,
+ "!$1! must be on the last of a couple of notes",
+ nm)
+ continue
+ }
+ }
// add the decoration in the symbol
if (!s.a_dd)
s.a_dd = []
s.a_dd.push(dd)
@@ -1160,10 +1247,53 @@
return
var de, dd, s, note, f, st, x, y, y2, ym, uf, i, str, a,
new_de = [],
ymid = []
+ // display a finger glissando
+ function out_fg() {
+ var k, l, de2, fg, fg2, x2,
+ j = s.fg.length
+
+ while (--j >= 0) {
+ fg = s.fg[j]
+ if (fg.nm == dd.name)
+ break
+ }
+ if (j < 0)
+ return
+
+ if (fg.ty) { // end
+ if (fg.ty == 1) // no start (not treated yet)
+ out_wln(x - 19, y, 12)
+ return
+ }
+
+ x2 = x + 7 // start
+ for (k = 0; k < a_de.length; k++) {
+ de2 = a_de[k]
+ if (de2.s != fg.s
+ || de2.dd.name != dd.name)
+ continue
+ for (l = 0; l < de2.s.fg.length; l++) {
+ fg2 = de2.s.fg[l]
+ if (fg2.nm == fg.nm)
+ break
+ }
+ if (fg2.nm == fg.nm) { // if same finger
+ fg2.ty = 2 // end done
+ xypath(x2, y + 1)
+ output += 'l' + (de2.x - 7 - x2).toFixed(1)
+ + ' ' + (y - de2.y
+ - staff_tb[s.st].y).toFixed(1)
+ + '" stroke-width=".7"/>\n'
+ return
+ }
+ }
+ out_wln(x2, y, 12) // start without end
+ } // out_fg()
+
st = nstaff;
y = staff_tb[st].y
while (--st >= 0) {
y2 = staff_tb[st].y;
ymid[st] = (y + 24 + y2) * .5;
@@ -1269,10 +1399,13 @@
// } else
if (de.inv) {
y = y + dd.h - dd.hd
g_open(x, y, 0, 1, -1);
x = y = 0
+ } else if (de.rotpi) {
+ g_open(x, y, 180)
+ x = y = 0
}
if (de.has_val) {
if (dd.func != 2 // if not !arpeggio!
|| stv_g.st < 0) // or not staff scale
// || voice_tb[s.v].scale != 1)
@@ -1282,10 +1415,12 @@
if (de.cont)
new_de.push(de.start) // to be continued next line
} else if (dd.str != undefined // string
&& !tgls[dd.glyph]
&& !glyphs[dd.glyph]) { // with a class
+ if (s.fg) // if finger glissando
+ out_fg() // (may change y)
out_deco_str(x, y, // - dd.h * .2,
de)
} else if (de.lden) {
out_deco_long(x, y, de)
} else {
@@ -2013,11 +2148,11 @@
}
/* -- draw the parts and the tempo information -- */
// (unscaled delayed output)
function draw_partempo() {
- var s, s2, some_part, some_tempo, h, w, y, st,
+ var s, s2, some_part, some_tempo, h, w, y, st, p,
sy = cur_sy
// search the top staff
for (st = 0; st <= nstaff; st++) {
if (sy.st_print[st])
@@ -2045,11 +2180,14 @@
h = gene.curfont.size + 2 +
gene.curfont.pad * 2
}
if (s2.x == undefined)
s2.x = s.x - 10
- w = strwh(s2.text)[0]
+ p = s2.text
+ if (cfmt.partname)
+ s2.ntxt = p = partname(p)[2]
+ w = strwh(p)[0]
y = y_get(st, true, s2.x, w + 3)
if (ymin < y)
ymin = y
}
if (some_part) {
@@ -2057,22 +2195,24 @@
ymin *= staff_tb[st].staffscale
for (s = some_part; s; s = s.ts_next) {
s2 = s.part
if (!s2 || s2.invis)
continue
- w = strwh(s2.text)[0]
+ p = s2.ntxt || s2.text
+ w = strwh(p)[0]
if (user.anno_start || user.anno_stop) {
s2.wl = 0
s2.wr = w
s2.ymn = ymin
s2.ymx = s2.ymn + h
anno_start(s2)
}
xy_str(s2.x,
ymin + 2 + gene.curfont.pad + gene.curfont.size * .22,
- s2.text)
- y_set(st, 1, s2.x, w + 3, ymin + 2 + h)
+ p)
+ y_set(st, 1, s2.x, w + 3,
+ (ymin + 2 + h) / staff_tb[st].staffscale)
if (s2.x < 0)
yn = ymin + 2 + h
anno_stop(s2)
}
}
@@ -2121,10 +2261,11 @@
s.ymx = s.ymn + 14
anno_start(s)
}
writempo(s, s.x - 16, y)
anno_stop(s)
- y_set(st, 1, s.x - 16, w, y + h + 2)
+ y_set(st, 1, s.x - 16, w,
+ (y + h + 2) / staff_tb[st].staffscale)
dosh >>= 1
}
}
}
Index: core/draw.js
==================================================================
--- core/draw.js
+++ core/draw.js
@@ -1,8 +1,8 @@
// abc2svg - draw.js - draw functions
//
-// Copyright (C) 2014-2023 Jean-Francois Moine
+// Copyright (C) 2014-2024 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
@@ -28,11 +28,11 @@
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 = 7, /* length of stub for flag under beam */
- SLUR_SLOPE = .5, /* max slope of a slur */
+ SLUR_SLOPE = .7, // 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
@@ -397,17 +397,15 @@
for (g = s.extra; g; g = g.next) {
y = a * g.x + b
if (s1.stem > 0) {
y = g.ymx - y
+ BEAM_DEPTH + BEAM_SHIFT * (nflags - 1)
- + 2
if (y > 0)
b += y
} else {
y = g.ymn - y
- BEAM_DEPTH - BEAM_SHIFT * (nflags - 1)
- - 2
if (y < 0)
b += y
}
}
break
@@ -424,20 +422,10 @@
switch (s.type) {
case C.NOTE:
s.ys = a * s.xs + b - staff_tb[s.st].y
if (s.stem > 0) {
s.ymx = s.ys + 2.5
-//fixme: hack
- if (s.ts_prev
- && s.ts_prev.stem > 0
- && s.ts_prev.st == s.st
- && s.ts_prev.ymn < s.ymx
- && s.ts_prev.x == s.x
- && s.notes[0].shhd == 0) {
- s.ts_prev.x -= 3; /* fix stem clash */
- s.ts_prev.xs -= 3
- }
} else {
s.ymn = s.ys - 2.5
}
break
case C.REST:
@@ -511,11 +499,11 @@
}
y1 = bm.a * x1 + bm.b - dy;
x2 -= x1;
x2 /= stv_g.scale;
- dy2 = bm.a * x2 * stv_g.scale
+ dy2 = bm.a * x2 * stv_g.stsc
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'
@@ -747,33 +735,65 @@
/* -- draw the time signature -- */
function draw_meter(s) {
if (!s.a_meter)
return
- var dx, i, j, meter, x,
- st = s.st,
- p_staff = staff_tb[st],
- y = p_staff.y;
+ var i, m, meter, x, x0, yt,
+ p_staff = staff_tb[s.st],
+ y = p_staff.y
// adjust the vertical offset according to the staff definition
if (p_staff.stafflines != '|||||')
y += (p_staff.topbar + p_staff.botbar) / 2 - 12 // bottom
for (i = 0; i < s.a_meter.length; i++) {
meter = s.a_meter[i];
x = s.x + s.x_meter[i]
-
+ yt = y + (meter.bot ? 18 : 12)
+ xygl(x, yt, "mtr" + meter.top[0])
+ if (meter.top.length > 1) {
+ m = 0
+ x0 = x
+ while (1) {
+ switch (meter.top[m]) {
+ case '(':
+ case ')':
+ x += 4
+ break
+ case '1':
+ x += 8
+ break
+ case ' ':
+ x += 4
+ break
+ case '+':
+ x += 2
+ // fall thru
+ default:
+ x += 10
+ break
+ }
+ if (++m >= meter.top.length)
+ break
+ xygl(x, yt, "mtr" + meter.top[m])
+ }
+ x = (x0 + x) / 2 - 5
+ }
if (meter.bot) {
- out_XYAB('\
-\n\
- A\n\
- B\n\
-\n', x, y + 6, m_gl(meter.top), m_gl(meter.bot))
- } else {
- out_XYAB('\
-A\n',
- x, y + 12, m_gl(meter.top))
+ if (meter.bot[1]) {
+ if (meter.bot[0] == 1) {
+ x0 = x - 4
+ x += 4
+ } else {
+ x0 = x - 5
+ x += 5
+ }
+ xygl(x0, y + 6, "mtr" + meter.bot[0])
+ xygl(x, y + 6, "mtr" + meter.bot[1])
+ } else {
+ xygl(x, y + 6, "mtr" + meter.bot[0])
+ }
}
}
}
var acc_nd = {} // cache of the microtonal accidentals
@@ -851,14 +871,14 @@
if (!n)
return // no
// handle the helper lines out of the staff
var dx1, dx2, hl, shhd,hlp,
- stafflines = p_staff.stafflines,
+ stafflines = cur_sy.staves[st].stafflines,
top = stafflines.length - 1,
yu = top,
- bot = p_staff.botline / 6,
+ bot = (p_staff.hll - 17) / 2,
yl = bot,
dx = s.grace ? 4 : hw_tb[s.head] * 1.3
// get the x start and x stop of the intermediate helper lines
note = s.notes[s.stem < 0 ? s.nhd : 0]
@@ -1512,11 +1532,11 @@
;
} else if (s.grace) { // don't apply %%map to grace notes
p = "ghd";
x_note -= 4.5 * stv_g.scale
} else if (note.map && note.map[0]) {
- i = s.head;
+ i = head;
p = note.map[0][i] // heads
if (!p)
p = note.map[0][note.map[0].length - 1]
i = p.indexOf('/')
if (i >= 0) { // stem dependant
@@ -1989,11 +2009,11 @@
}
} else {
y1 = k1.ys - 3
}
} else {
- y1 = k1.y - 8
+ y1 = k1.y - 5
}
}
}
}
@@ -2024,11 +2044,11 @@
// || k2.ys < y2 + 3))
y2 = k2.ys + 6
else
y2 = k2.ys - 3
} else {
- y2 = k2.y - 8
+ y2 = k2.y - 5
}
}
}
}
@@ -2107,11 +2127,11 @@
/* special case for grace notes */
if (k1.grace)
x1 = k1.x - GSTEM_XOFF * .5
if (k2.grace)
- x2 = k2.x + GSTEM_XOFF * 1.5;
+ x2 = k2.x + GSTEM_XOFF * .5
h = 0;
a = (y2 - y1) / (x2 - x1)
if (k1 != k2
&& k1.v == k2.v) {
@@ -2138,19 +2158,19 @@
}
break
case C.GRACE:
for (g = k.extra; g; g = g.next) {
if (dir > 0) {
- y = 3 * (g.notes[g.nhd].pit - 18) + 6
- if (y < g.ymx)
+// y = 3 * (g.notes[g.nhd].pit - 18) + 6
+// if (y < g.ymx)
y = g.ymx;
y -= a * g.x + addy
if (y > h)
h = y
} else {
- y = 3 * (g.notes[0].pit - 18) - 6
- if (y > g.ymn)
+// y = 3 * (g.notes[0].pit - 18) - 6
+// if (y > g.ymn)
y = g.ymn;
y -= a * g.x + addy
if (y < h)
h = y
}
@@ -2201,14 +2221,14 @@
/*fixme: it seems to work with .4, but why?*/
// addy = y1 - a * x1 + .4 * height
//fixme: the following code seems better!
addy = y1 - a * x1
if (height > 0)
- addy += 4 * Math.sqrt(height) - 2
+ addy += 3 * Math.sqrt(height) - 2
else
- addy -= 4 * Math.sqrt(-height) - 2
- for (i = 0; i < i2; i++) {
+ addy -= 3 * Math.sqrt(-height) - 2
+ for (i = 0; i <= i2; i++) {
k = path[i]
if (k.st != k1.st || k.type == C.BAR)
continue
y = a * k.x + addy
if (k.ymx < y)
@@ -2215,11 +2235,11 @@
k.ymx = y
else if (k.ymn > y)
k.ymn = y
if (recurr) // no room when slur on 2 staves
continue
- if (i == i2 - 1) {
+ if (i == i2) {
dx = x2
if (sl.nte)
dx -= 5;
} else {
dx = k.x + k.wr
@@ -2860,13 +2880,12 @@
if (s1.dots && !(not1.pit & 1)
&& ((dir > 0 && !s1.dot_low)
|| (dir < 0 && s1.dot_low)))
x1 += 5
- y = staff_tb[st].y + 3 * (p - 18) + /* 1.0 * */ dir
-
- h = (.03 * (x2 - x1) + 16) * dir * s1.fmt.tieheight
+ y = staff_tb[st].y + 3 * (p - 18) + .8 * dir
+ h = (.15 * (x2 - x1) + 14) * dir * s1.fmt.tieheight
// anno_start(k1, 'slur')
slur_out(x1, y, x2, y, dir, h, not1.tie_ty & C.SL_DOTTED)
// anno_stop(k1, 'slur')
} // draw_tie()
@@ -2879,10 +2898,11 @@
// clef_chg = false
s1 = p_voice.sym
set_color(s1.color)
for ( ; s1; s1 = s1.next) {
if (s1.ti2 // if end of tie
+ && !s1.invis
&& s1.time != tim2) { // and new end
for (m = 0; m <= s1.nhd; m++) {
not2 = s1.notes[m]
not1 = not2.tie_s
if (!not1
@@ -2889,11 +2909,12 @@
|| not1.s.v != s1.v) // (already done)
continue
draw_tie(not1, not2, 1)
}
}
- if (!s1.ti1) // if not start of tie
+ if (!s1.ti1 // if not start of tie
+ || s1.invis)
continue
// get the end of the tie(s)
if (s1.type == C.GRACE) {
for (s = s1.extra; s; s = s.next) {
@@ -3074,10 +3095,14 @@
y_set(s.st, true, s.x + 16, 32, s.ymx + 2)
continue
default:
y_set(s.st, true, s.x - s.wl, s.wl + s.wr, s.ymx + 2);
y_set(s.st, false, s.x - s.wl, s.wl + s.wr, s.ymn - 2)
+ // fall thru
+ case C.PART:
+ case C.TEMPO:
+ case C.STAVES:
continue
case C.NOTE:
break
}
@@ -3213,13 +3238,11 @@
if (!cur_sy.voices[v])
continue
st = cur_sy.voices[v].st
if (!stl[st])
continue
- if (!gene.vnt)
- continue
- p = gene.vnt == 2 ? p_voice.nm : p_voice.snm
+ p = gene.vnt == 1 ? p_voice.nm : p_voice.snm
if (!p)
continue
delete p_voice.new_name
if (!staff_d[st])
staff_d[st] = p
@@ -3237,11 +3260,11 @@
continue
a_p = staff_d[st].split("\n");
y = staff_tb[st].y
+ staff_tb[st].topbar * .5
* staff_tb[st].staffscale
- + h2 * (a_p.length - 2)
+ + h2 * (a_p.length - 2) + h *.22
// if instrument with 2 staves, center the voice name
if ((cur_sy.staves[st].flags & OPEN_BRACE)
&& st + 1 < staff_tb.length
&& (cur_sy.staves[st + 1].flags & CLOSE_BRACE)
@@ -3395,46 +3418,109 @@
var s, s2, st, x, x2, res, sy,
xstaff = [],
stl = [], // all staves in the line
bar_bot = [],
bar_height = [],
+ bar_ng = [], // number of gaps
ba = [], // bars [symbol, bottom, height]
sb = "",
thb = ""
/* -- set the bottom and height of the measure bars -- */
function bar_set() {
- var st, staffscale, top, bot,
- dy = 0
+ var st, sc, i, j, l, stlines, b, hlmap,
+ dy = 0
for (st = 0; st <= cur_sy.nstaff; st++) {
if (xstaff[st] < 0) {
bar_bot[st] = bar_height[st] = 0
continue
}
- staffscale = staff_tb[st].staffscale;
- top = staff_tb[st].topbar * staffscale;
- bot = staff_tb[st].botbar * staffscale
- if (dy == 0)
- dy = staff_tb[st].y + top;
- bar_bot[st] = staff_tb[st].y + bot;
+ sc = staff_tb[st].staffscale;
+ stlines = cur_sy.staves[st].stafflines
+ l = stlines.length
+ for (i = 0; i < l; i++) {
+ if (stlines[i] != '.' && stlines[i] != '-')
+ break
+ }
+ if (i >= l - 1) // 0 or 1 line
+ bar_bot[st] = staff_tb[st].y + 6 * (l - 2) * sc
+ else if (i == l - 2) // 2 lines
+ bar_bot[st] = staff_tb[st].y + 6 * (l - 3) * sc
+ else
+ bar_bot[st] = staff_tb[st].y + 6 * i * sc
+ if (!dy) {
+ if (i >= l - 2) { // 0, 1 or 2 lines
+ dy = staff_tb[st].y + 6 * l * sc
+ } else {
+ dy = staff_tb[st].y + 6 * (l - 1) * sc
+ }
+ }
bar_height[st] = dy - bar_bot[st];
+ bar_ng[st] = l - i && l - 1 - i // number of gaps
+
+ // define the helper lines
+ if (stlines[l-1]!= '.') { // if any staff line
+ staff_tb[st].hll = 17 + i * 2 // pitch of lowest note
+ // without helper line
+ // ('D' when standard staff)
+ if (i == l)
+ staff_tb[st].hll -= 2
+ staff_tb[st].hlmap =
+ hlmap = new Int8Array((l - i + 1) * 2 + 2)
+ for (j = 1; i < l; i++, j += 2) {
+ switch (stlines[i]) {
+ case '|':
+ case '[':
+ case "'":
+// case ':':
+ hlmap[j - 1] = 1 // no helper line
+ hlmap[j] = 1
+ hlmap[j + 1] = 1
+ break
+ }
+ }
+ }
+
dy = (cur_sy.staves[st].flags & STOP_BAR) ?
0 : bar_bot[st]
}
+
+ // if in the middle of the tune, check the previous bar(s)
+ i = ba.length
+ if (!i)
+ return
+ while (--i >= 0) {
+ b = ba[i]
+ st = b[0].st
+ if (b[1] > bar_bot[st])
+ b[1] = bar_bot[st]
+ if (b[2] < bar_height[st])
+ b[2] = bar_height[st]
+ if (b[3] < bar_ng[st])
+ b[3] = bar_ng[st]
+ if (b[0].seqst) // end of time sequence
+ break
+ }
} // bar_set()
/* -- draw a staff -- */
function draw_staff(st, x1, x2) {
var w, i, dy, ty,
y = 0,
ln = "",
- stafflines = staff_tb[st].stafflines,
+ tycl = {
+ "|": "slW", // normal
+ "[": "slthW", // thick
+ "'": "sltnW", // thin
+ ":": "sldW" // dash
+ },
+ stafflines = cur_sy.staves[st].stafflines,
l = stafflines.length,
il = 6 * staff_tb[st].staffscale // interline
- if (!/[\[|]/.test(stafflines))
+ if (!/[\[|':]/.test(stafflines)) // '
return // no line
w = x2 - x1;
set_sscale(-1)
// check if default staff
@@ -3461,12 +3547,11 @@
continue
}
if (ty != undefined)
ln += '"/>\n';
ty = stafflines[i]
- ln += ''
}
@@ -3488,36 +3573,35 @@
}
out_XYAB('\n' + ln + '\n\n', x1, y)
} // draw_staff()
// draw a measure bar
- function draw_bar(s, bot, h) {
+ function draw_bar(s, bot, h, ng) {
var i, s2, yb, w,
bar_type = s.bar_type,
st = s.st,
p_staff = staff_tb[st],
+ top = ng >= 3 ? 6 * ng : ng == 1 ? 18 : 12,
x = s.x
// don't put a line between the staves if there is no bar above
if (st != 0
&& s.ts_prev
//fixme: 's.ts_prev.st != st - 1' when floating voice in lower staff
// && (s.ts_prev.type != C.BAR || s.ts_prev.st != st - 1))
&& s.ts_prev.type != C.BAR)
- h = p_staff.topbar * p_staff.staffscale;
+ h = top * p_staff.staffscale
s.ymx = s.ymn + h;
set_sscale(-1)
anno_start(s)
if (s.color)
set_color(s.color);
// compute the middle vertical offset of the staff
- yb = p_staff.y + 12;
- if (p_staff.stafflines != '|||||')
- yb += (p_staff.topbar + p_staff.botbar) / 2 - 12 // bottom
+ yb = bot + top / 2
// if measure repeat, draw the '%' like glyphs
if (s.bar_mrep) {
set_sscale(st)
if (s.bar_mrep == 1) {
@@ -3537,39 +3621,47 @@
for (i = bar_type.length; --i >= 0; ) {
switch (bar_type[i]) {
case "|":
if (s.bar_dotted) {
- w = (5 * p_staff.staffscale).toFixed(1);
+ w = top / 6 <= 9
+ ? [0, 0, 4, 3.6, 4.8, 4.3, 4, 4.7, 4.4, 4.9]
+ [top / 6]
+ : 5
out_XYAB(
- '\n',
- x, bot, w, h)
+ '\n',
+ x, bot, w * p_staff.staffscale, -h)
} else if (s.color) {
- out_XYAB('\n',
- x, bot, h)
+ out_XYAB('\n',
+ x, bot, -h)
} else {
sb += 'M' + sx(x).toFixed(1)
+ ' ' + self.sy(bot).toFixed(1)
- + 'v-' + h.toFixed(1)
+ + 'v' + (-h).toFixed(1)
}
break
default:
// case "[":
// case "]":
x -= 3;
if (s.color)
- out_XYAB('\n',
- x + 1.5, bot, h)
+ out_XYAB('\n',
+ x + 1.5, bot, -h)
else
thb += 'M' + sx(x + 1.5).toFixed(1)
+ ' ' + self.sy(bot).toFixed(1)
- + 'v-' + h.toFixed(1)
+ + 'v' + (-h).toFixed(1)
break
case ":":
x -= 2;
set_sscale(st);
- xygl(x + 1, yb - 12, "rdots")
+ if (ng & 1) {
+ xygl(x, yb + 6, "rdot")
+ xygl(x, yb - 6, "rdot")
+ } else {
+ xygl(x, yb - 12, "rdots")
+ }
set_sscale(-1)
break
}
x -= 3
}
@@ -3585,12 +3677,12 @@
set_font("annotation");
bx = gene.curfont.box
if (bx)
gene.curfont.box = 0
for (i = 0; i < l; i++) {
- b = ba[i]; // symbol, bottom, height
- draw_bar(b[0], b[1], b[2])
+ b = ba[i] // symbol, bottom, height, top
+ draw_bar(b[0], b[1], b[2], b[3])
}
if (bx)
gene.curfont.box = bx
set_sscale(-1)
@@ -3606,28 +3698,31 @@
} // out_bars()
// set the helper lines of rests
function hl_rest(s) {
var j,
+ stlines = cur_sy.staves[s.st].stafflines,
p_st = staff_tb[s.st],
i = 5 - s.nflags, // rest_tb index (5 = C_XFLAGS)
x = s.x,
y = s.y
if (i < 6) // no ledger line if rest smaller than minim
return
if (i == 7 && y == 12
- && p_st.stafflines.length <= 2)
+ && stlines.length <= 2)
y -= 6 // semibreve a bit lower
j = y / 6
switch (i) {
default:
- switch (p_st.stafflines[j + 1]) {
+ switch (stlines[j + 1]) {
case '|':
case '[':
+ case "'":
+ case ':':
break
default:
set_hl(p_st, j + 1, x, -7, 7)
break
}
@@ -3640,13 +3735,15 @@
y += 6
j++
case 6: // minim
break
}
- switch (p_st.stafflines[j]) {
+ switch (stlines[j]) {
case '|':
case '[':
+ case "'":
+ case ':':
break
default:
set_hl(p_st, j, x, -7, 7)
break
}
@@ -3695,10 +3792,13 @@
&& sy.staves[st].stafflines ==
cur_sy.staves[st].stafflines)
continue
if (s.ts_prev.bar_type) {
x2 = s.ts_prev.x
+ if (sy.staves[st].stafflines.length >
+ cur_sy.staves[st].stafflines.length)
+ x2 -= s.ts_prev.wl - 4
} else {
x2 = (s.ts_prev.x + s.x) / 2
xstaff[st] = -1
}
draw_staff(st, x, x2)
@@ -3714,11 +3814,11 @@
if (s.second
&& (!s.ts_prev
|| (s.ts_prev.type == C.BAR
&& s.ts_prev.st == s.st)))
break
- ba.push([s, bar_bot[s.st], bar_height[s.st]])
+ ba.push([s, bar_bot[s.st], bar_height[s.st], bar_ng[s.st]])
break
case C.STBRK:
if (cur_sy.voices[s.v]
&& cur_sy.voices[s.v].range == 0) {
if (s.xmx > 14
Index: core/format.js
==================================================================
--- core/format.js
+++ core/format.js
@@ -1,8 +1,8 @@
// abc2svg - format.js - formatting functions
//
-// Copyright (C) 2014-2023 Jean-Francois Moine
+// Copyright (C) 2014-2024 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
@@ -24,10 +24,11 @@
'sans-serifBold': 1,
Palatino: 1.1,
monospace: 1
},
txt_ff = "text,serif", // text font-family (serif for compatibility)
+ ff = {}, // font-face's from %%beginsvg
fmt_lock = {}
var cfmt = {
"abc-version": "1", // default: old version
annotationfont: {name: "text,sans-serif", size: 12},
@@ -107,11 +108,12 @@
stretchlast: .25,
stretchstaff: true,
subtitlefont: {name: txt_ff, size: 16},
subtitlespace: 3,
sysstaffsep: 34,
- systnames: -1,
+ systnames: -1, // (for compatibility)
+ systvoices: 3,
tempofont: {name: txt_ff, weight: "bold", size: 12},
textfont: {name: txt_ff, size: 16},
// textoption: undefined,
textspace: 14,
tieheight: 1.0,
@@ -154,11 +156,11 @@
rbmin: true,
shiftunison: true,
slurheight: true,
squarebreve: true,
staffsep: true,
-systnames: 1, //
+systvoices: 1, //true
stemheight: true,
stretchlast: true,
stretchstaff: true,
tieheight: true,
timewarn: true,
@@ -388,14 +390,14 @@
return v[1]
}
return NaN
}
-// set the infoname
-function set_infoname(param) {
+// set the name of an info or a part
+function set_infoname(cmd, param) {
//fixme: check syntax: ' ["string"]'
- var tmp = cfmt.infoname.split("\n"),
+ var tmp = cfmt[cmd] ? cfmt[cmd].split("\n") : "",
letter = param[0]
for (var i = 0; i < tmp.length; i++) {
var infoname = tmp[i]
if (infoname[0] != letter)
@@ -402,14 +404,17 @@
continue
if (param.length == 1)
tmp.splice(i, 1)
else
tmp[i] = param
- cfmt.infoname = tmp.join('\n')
+ cfmt[cmd] = tmp.join('\n')
return
}
- cfmt.infoname += "\n" + param
+ if (cfmt[cmd])
+ cfmt[cmd] += "\n" + param
+ else
+ cfmt[cmd] = param
}
// get the text option
var textopt = {
align: 'j',
@@ -532,14 +537,23 @@
case "rbmax":
case "rbmin":
case "measrepnb":
case "shiftunison":
case "systnames":
+ case "systvoices":
v = parseInt(param)
if (isNaN(v)) {
syntax(1, "Bad integer value");
break
+ }
+ if (cmd == "systnames") { // compatibility
+ switch (v) {
+ case -1: v = 3; break
+ case 1: v = 2; break
+ case 2: v = 1; break
+ }
+ cmd = "systvoices"
}
cfmt[cmd] = v
break
case "abc-version":
case "bgcolor":
@@ -595,11 +609,10 @@
case "graceslurs":
case "graceword":
case "hyphencont":
case "keywarn":
case "linewarn":
- case "quiet":
case "squarebreve":
case "splittune":
case "straightflags":
case "stretchstaff":
case "timewarn":
@@ -642,11 +655,11 @@
case "titlespace":
case "topspace":
case "vocalspace":
case "wordsspace":
f = get_unit(param) // normally, unit in points - 72 DPI accepted
- if (isNaN(f))
+ if (isNaN(f) || f < 0)
syntax(1, errs.bad_val, '%%' + cmd)
else
cfmt[cmd] = f
break
case "page-format":
@@ -723,11 +736,12 @@
curvoice.tup = v
else
cfmt[cmd] = v
break
case "infoname":
- set_infoname(param)
+ case "partname":
+ set_infoname(cmd, param)
break
case "notespacingfactor":
v = param.match(/([.\d]+)[,\s]*(\d+)?/)
if (v) {
f = +v[1]
@@ -819,16 +833,17 @@
break
case "textoption":
cfmt[cmd] = get_textopt(param)
break
case "dynalign":
+ case "quiet":
case "singleline":
case "stretchlast":
case "titletrim":
- v = +param
+ v = param == '' ? 1 : +param
if (isNaN(v))
- v = get_bool(param) ? 0 : 1
+ v = +get_bool(param)
if (cmd[1] == 't') { // stretchlast
if (v < 0 || v > 1) {
syntax(1, errs.bad_val, '%%' + cmd)
break
}
@@ -910,12 +925,14 @@
abc2svg.font_tb.push(font)
if (!font.swfac)
set_font_fac(font)
if (!font.pad)
font.pad = 0
+ }
// set the pointer to the width of the characters
+ if (!font.cw_tb) {
font.cw_tb = !font.name ? ssw_tb
: font.name.indexOf("ans") > 0
? ssw_tb // sans-serif
: font.name.indexOf("ono") > 0
? mw_tb // monospace
@@ -930,10 +947,14 @@
src:" + font.src + "}")
if (font == cfmt.musicfont) // add more music font style
add_fstyle(".f" + font.fid
+ (cfmt.fullsvg || "")
+ ' text,tspan{white-space:pre}')
+ if (ff.text && !ff.used && font.name.indexOf("text") >= 0) {
+ font_style += ff.text // add font-face's from %%beginsvg
+ ff.used = 1 //true
+ }
}
}
// get the font of the 'xxxfont' parameter
function get_font(fn) {
@@ -953,16 +974,15 @@
if (font.normal) {
if (font2.weight) // !! don't use delete !!
font2.weight = null
if (font2.style)
font2.style = null
- } else {
+ }
if (font.weight)
font2.weight = font.weight
if (font.style)
font2.style = font.style
- }
if (font.src)
font2.src = font.src
if (font.size)
font2.size = font.size
st = st_font(font2)
Index: core/front.js
==================================================================
--- core/front.js
+++ core/front.js
@@ -215,10 +215,11 @@
}
include++;
parse_sav = clone(parse);
tosvg(fn, file);
parse_sav.state = parse.state;
+ parse_sav.ckey = parse.ckey
parse = parse_sav;
include--
}
// parse ABC code
@@ -269,12 +270,49 @@
src = src.replace(/\s+$/, '')
if (flag && src.indexOf('\\') >= 0)
return cnv_escape(src, flag)
return src
} // uncomment()
+
+ // set the sequence showing the source and save it in sav.src
+ function set_src(stag, se) {
+ var r, t,
+ etag = ""
+
+ if (!se)
+ se = file.indexOf('\n\n', bol) // end of tune
+ if (se < 0)
+ se = eof
+ if (typeof stag != "object") { // set the tag after source
+ if (stag[0] != 'b' && stag[0] != 'a' && stag[0] != '+'
+ && stag[0] != '*')
+ stag = 'b' + stag // default: source before
+ if (stag[1] != '<') // (if bool)
+ stag = stag[0] + "
"
+ r = stag.match(/<\/?[^>]*>/g)
+ while (1) {
+ t = r.pop()
+ if (!t)
+ break
+ if (t[1] == '/' || t.slice(-2) == '/>')
+ r.pop() // skip this stop/start tag
+ else
+ etag += '' + t.slice(1)
+ }
+ cfmt.show_source = stag = [stag, etag]
+ }
+ t = stag[0].slice(1)
+ + clean_txt(file.slice(bol, se))
+ + stag[1]
+ if (stag[0][0] == '+' && sav.src)
+ sav.src += t
+ else
+ sav.src = t
+ } // set_src()
function end_tune() {
+ parse.bol = bol // (for multi V:)
generate()
cfmt = sav.cfmt;
info = sav.info;
char_tb = sav.char_tb;
glovar = sav.glovar;
@@ -286,10 +324,15 @@
parse.ufmt = false
delete parse.pq
init_tune()
img.chg = true;
set_page();
+ if (cfmt.show_source) {
+ user.img_out("")
+ if (cfmt.show_source[0][0] == 'a')
+ user.img_out(sav.src)
+ }
} // end_tune()
// get %%voice
function do_voice(select, in_tune) {
var opt, bol
@@ -498,16 +541,34 @@
if (parse.eol < 0)
parse.eol = eof
continue
}
switch (a[1]) {
+ case "show_source":
+ b = uncomment(a[2])
+ switch (b[0]) {
+ case '*':
+ i = file.indexOf('\n' + line0 + line1
+ + "show_source", eol)
+ bol = parse.eol
+ set_src(b, i)
+ user.img_out(sav.src)
+ // fall thru
+ case '0':
+ b = ""
+ // fall thru
+ default:
+ cfmt[a[1]] = b
+ // fall thru
+ }
+ continue
case "select":
if (parse.state != 0) {
syntax(1, errs.not_in_tune, "%%select")
continue
}
- select = uncomment(text.slice(7))
+ select = uncomment(a[2])
if (select[0] == '"')
select = select.slice(1, -1);
if (!select) {
delete parse.select
continue
@@ -641,15 +702,19 @@
sav.char_tb = clone(char_tb);
sav.glovar = clone(glovar);
sav.maps = clone(maps, 1);
sav.mac = clone(mac);
sav.maci = clone(maci);
+ if (cfmt.show_source) {
+ bol -= 2
+ set_src(cfmt.show_source)
+ if (cfmt.show_source[0][0] == 'b')
+ user.img_out(sav.src)
+ user.img_out('
')
+ }
info.X = text;
parse.state = 1 // tune header
- if (user.page_format
- && blkdiv < 1) // (if no newpage)
- blkdiv = 1 // the tune starts by the next SVG
if (parse.tune_opts)
tune_filter()
continue
case 'T':
switch (parse.state) {
@@ -747,8 +812,12 @@
syntax(1, "End of file in tune header")
get_key("C")
}
if (parse.state >= 2)
end_tune();
+ if (sav.src && cfmt.show_source[0] == '+') {
+ user.img_out(sav.src) // source of all tunes
+ sav.src = null
+ }
parse.state = 0
}
Abc.prototype.tosvg = tosvg
Index: core/lyrics.js
==================================================================
--- core/lyrics.js
+++ core/lyrics.js
@@ -1,8 +1,8 @@
// abc2svg - lyrics.js - lyrics
//
-// Copyright (C) 2014-2023 Jean-Francois Moine
+// Copyright (C) 2014-2024 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
@@ -119,12 +119,12 @@
}
curvoice.sym_cont = s
}
/* -- parse a lyric (vocal) line (w:) -- */
-function get_lyrics(text, cont) {
- var s, word, p, i, j, ly, dfnt, ln, c, cf
+function get_lyrics(p, cont) {
+ var s, word, i, j, ly, dfnt, ln, c, cf
if (curvoice.ignore)
return
if ((curvoice.pos.voc & 0x07) != C.SL_HIDDEN)
curvoice.have_ly = true
@@ -133,10 +133,16 @@
if (cont) { // +:
s = curvoice.lyric_cont
if (!s) {
syntax(1, "+: lyric without music")
return
+ }
+ if (p[0] == '~') { // +:~next~words
+ while (!s.a_ly)
+ s = s.prev
+ ly = s.a_ly[curvoice.lyric_line]
+ p = ly.t.replace(/ /g,'~') + p
}
dfnt = get_font("vocal")
if (gene.deffont != dfnt) { // if vocalfont change
if (gene.curfont == gene.deffont)
gene.curfont = dfnt
@@ -159,11 +165,10 @@
return
}
}
/* scan the lyric line */
- p = text;
i = 0
cf = gene.curfont
while (1) {
while (p[i] == ' ' || p[i] == '\t')
i++
@@ -357,11 +362,11 @@
s3.wl = wl
}
// if not room enough, shift the following notes to the right
dx -= 6
- if (dx < wx) {
+ if (dx < wx && s2) {
dx = (wx - dx) / n
s1 = s.ts_next
while (1) {
if (s1.shrink) {
s1.shrink += dx
Index: core/modules.js
==================================================================
--- core/modules.js
+++ core/modules.js
@@ -51,10 +51,11 @@
roman: {},
soloffs: {},
sth: {},
strtab: {},
temperament: { fn: 'temper' },
+ temponame: { fn: 'tempo' },
tropt: {},
nreq: 0,
// scan the file and find the required modules
@@ -84,11 +85,11 @@
}
// test if some keyword in the file
var m, i, fn,
nreq_i = this.nreq,
- ls = file.match(/(^|\n)(%%|I:).+?\b/g)
+ ls = file.match(/(%%|I:).+?\b/g)
if (!ls)
return true
this.cbf = relay || // (only one callback function)
function(){}
Index: core/music.js
==================================================================
--- core/music.js
+++ core/music.js
@@ -65,10 +65,14 @@
7 // SQUARE
])
// get head type, dots, flags of note/rest for a duration
function identify_note(s, dur_o) {
+ var r = abc2svg.hdn[dur_o]
+ if (r) // in cache?
+ return r
+
var head, flags,
dots = 0,
dur = dur_o
if (dur % 12 != 0)
@@ -79,14 +83,10 @@
for (flags = 5; dur; dur >>= 1, flags--) {
if (dur & 1)
break
}
dur >>= 1
- if ((dur + 1) & dur) {
- if (s.type != C.REST || dur_o != s.p_v.wmeasure)
- error(0, s, "Non standard note duration $1", dur_o)
- }
while (dur >> dots > 0)
dots++
flags -= dots
if (flags >= 0) {
@@ -107,11 +107,13 @@
break
case -1:
head = C.EMPTY
break
}
- return [head, dots, flags]
+ abc2svg.hdn[dur_o] =
+ r = [head, dots, flags]
+ return r
}
function set_head_shift(s) {
var i, i1, i2, d, ps, dx,
dx_head = dx_tb[s.head],
@@ -865,10 +867,13 @@
case ":":
w += 2
break
}
}
+ if (bar_type[0] == ":" // if "c3 :|"
+ && s.prev && s.prev.dots)
+ w += 4
break
}
s.wl = w
if (s.next
&& s.next.type != C.METER)
@@ -901,14 +906,14 @@
/* have room for the repeat numbers / chord indication */
if (s.text && s.text.length < 4
&& s.next && s.next.a_gch) {
set_font("repeat");
s.wr += strwh(s.text)[0] + 2
- if (cfmt.measurenb > 0 & s.bar_num
- && s.bar_num % cfmt.measurenb)
+ }
+ if (cfmt.measurenb > 0 && s.bar_num
+ && !(s.bar_num % cfmt.measurenb))
s.wr += 4
- }
return
case C.CLEF:
// (there may be invisible clefs in empty staves)
if (s.invis) {
s.wl = s.wr = 1 // (!! not 0 !!)
@@ -983,16 +988,16 @@
meter = s.a_meter[i]
switch (meter.top[0]) {
case 'C':
case 'c':
case 'o':
- s.x_meter[i] = wlw + 6;
+ s.x_meter[i] = wlw
wlw += 12
break
case '.':
case '|':
- s.x_meter[i] = s.x_meter[i - 1]
+ s.x_meter[i] = s.x_meter[i - 1] + 4.5
break
default:
w = 0
if (!meter.bot
|| meter.top.length > meter.bot.length)
@@ -1000,22 +1005,29 @@
else
meter = meter.bot;
for (m = 0; m < meter.length; m++) {
switch (meter[m]) {
case '(':
- wlw += 4
- // fall thru
+ wlw += 2
+ w += 6
+ break
case ')':
+ wlw -= 2
+ w += 6
+ break
case '1':
- w += 4
+ w += 6
+ break
+ case ' ':
+ w += 2
break
default:
w += 12
break
}
}
- s.x_meter[i] = wlw + w / 2
+ s.x_meter[i] = wlw
wlw += w
}
}
s.wl = 1
s.wr = wlw + 7
@@ -1263,17 +1275,18 @@
b.wl = 0
b.wr = 0
b.ts_prev = s
b.next = s.next
b.ts_next = s.ts_next
- 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.shrink = sn.shrink
+ sn.shrink = sn.wl + 10
b.space = sn.space * .9 - 3
return b
}
/* -- set the width and space of all symbols -- */
@@ -1306,12 +1319,11 @@
wr[st] = wr[s.prev.st]
}
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)
+ if (s.dur && s.dur != s.notes[0].dur) // if in tuplet
itup = 1
s = s.ts_next
} while (s && !s.seqst);
// set the spaces of the time sequence
@@ -1322,10 +1334,12 @@
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 (itup) {
+ if (!first)
+ break
if (!stup)
stup = s2
} else if (stup && stup.v == s2.v) {
set_sp_tup(stup, s2)
stup = null
@@ -1335,11 +1349,12 @@
if (s2.type == C.CLEF
&& !s2.ts_prev.bar_type) {
delete s2.seqst; /* no space */
s2.time = tim
} else {
- s2.shrink = 10 // cannot be null
+ s2.shrink = s2.wl // cannot be null
+ maxx += s2.wl
}
}
tim = s2.time
if (!s)
break
@@ -1364,11 +1379,11 @@
if (stup)
set_sp_tup(stup, s2)
// let the chord symbols at the same offset
// and adjust the spacing due to the lyrics
- if (first && s_chs)
+ if (s_chs)
set_w_chs(s_chs)
}
// insert a rest, this one replacing a sequence or a measure
function to_rest(so) {
@@ -1819,11 +1834,11 @@
// advance in the next line
for (s2 = s; s2 && s2.time == s.time; s2 = s2.ts_next) {
switch (s2.type) {
case C.KEY:
- if (!s.fmt.keywarn
+ if (!s2.fmt.keywarn
|| s2.invis)
continue
for (s1 = s.ts_prev; s1 ;s1 = s1.ts_prev) {
if (s1.type != C.METER)
break
@@ -1838,11 +1853,11 @@
// 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
+ if (s2.clef_none) // 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] == ':')
@@ -2027,10 +2042,67 @@
indent) { /* for start of tune */
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 shrunk
+
+ // split a big lyric word on two music lines
+ function ly_split(s, wmax) {
+ var i, wh,
+ s2 = clone(s),
+ p = s.a_ly[0].t, // lyric word
+ w = 0,
+ j = 0
+
+ gene.deffont = s.a_ly[0].font
+ while (1) {
+ i = p.indexOf(' ', j) + 1
+ if (i <= 0)
+ break
+ wh = strwh(p.slice(j, i))
+ w += wh[0]
+ if (w > wmax)
+ break
+ j = i
+ }
+ s.a_ly[0].t = new String(p.slice(0, j - 1))
+ s2.a_ly = clone(s.a_ly)
+ s2.a_ly[0] = clone(s.a_ly[0])
+ s2.a_ly[0].t = new String(p.slice(j))
+ if (abc2svg.el) {
+ strwh(s.a_ly[0].t)
+ strwh(s2.a_ly[0].t)
+ } else {
+ s.a_ly[0].t.wh = strwh(s.a_ly[0].t)
+ s2.a_ly[0].t.wh = strwh(s2.a_ly[0].t)
+ }
+ w = s.a_ly[0].t.wh[0] // new length of the words
+ s.wr = wmax
+ s2.wr -= w
+
+ lkvsym(s2, s2.next) // voice linkage
+
+ s2.time += .1
+// s2.dur -= .1
+ s2.nl = 0 //false
+ while (!s.seqst)
+ s = s.ts_prev
+ s2.x = s.x + w
+ s2.shrink = w - s2.wl
+ s = s2.ts_next
+ while (s) {
+ if (s.seqst) {
+ s.wl -= w
+ s.shrink -= w
+ break
+ }
+ s = s.ts_next
+ }
+ lktsym(s2, s2.ts_next) // time linkage
+ } // ly_split()
+
+ // -- set_lines --
// 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
@@ -2072,10 +2144,21 @@
if (s == next) {
if (s)
s = set_nl(s)
return s
}
+
+ // if the width of the symbol is greater than
+ // the remaining width in the staff
+ // and if there are lyrics, split these lyrics on 2 staves
+ if (s.x > xmax
+ && s.prev.a_ly) {
+ s3 = s = s.prev
+ while (!s3.seqst)
+ s3 = s3.ts_prev
+ ly_split(s, xmax - s3.x)
+ } else {
/* try to cut on a measure bar */
s3 = null
for ( ; s != next; s = s.ts_next) {
x = s.x
@@ -2145,10 +2228,11 @@
}
s3 = s
}
}
s = s3
+ } // ly_split call
while (s.ts_next) {
s = s.ts_next
if (s.seqst)
break
}
@@ -2181,16 +2265,16 @@
leftmargin: cfmt.leftmargin,
rightmargin: cfmt.rightmargin,
pagewidth: cfmt.pagewidth,
scale: cfmt.scale
},
- indent = lsh[0] - lsh[1], // extra width of the first line
+ indent = lsh[1] - lsh[2], // extra width of the first line
ckw = get_ck_width(), // width of the starting symbols
s = tsfirst
- lwidth -= lsh[1] // width of the lines
- if (cfmt.indent && cfmt.indent > lsh[0])
+ lwidth -= lsh[2] // width of the lines
+ if (cfmt.indent && cfmt.indent > lsh[1])
indent += cfmt.indent
// adjust the line width according to the starting symbols
lwidth -= ckw[0]
indent += ckw[1]
@@ -2568,12 +2652,12 @@
// set the clefs (treble or bass) in a 'auto clef' sequence
// return the starting clef type
function set_auto_clef(st, s_start, clef_type_start) {
var s, time, s2, s3,
- max = 12, /* "F," */
- min = 20 /* "G" */
+ max = 14, // "A,"
+ min = 18 // "E"
/* get the max and min pitches in the sequence */
for (s = s_start; s; s = s.ts_next) {
if (s.type == C.STAVES && s != s_start)
break
@@ -3145,11 +3229,21 @@
break
if (s.ts_next.a_dd)
s.a_dd = s.ts_next.a_dd
if (s.ts_next.a_gch)
s.a_gch = s.ts_next.a_gch
- unlksym(s.ts_next)
+ if (s.p_v.scale != 1
+ && s.ts_next.p_v.scale > s.p_v.scale)
+ unlksym(s)
+ else
+ unlksym(s.ts_next)
+
+ if ((!s.ts_prev.dur || s.ts_prev.time != s.time
+ || s.ts_prev.st != s.st)
+ && (s.ts_next.time != s.time
+ || s.ts_next.st != s.st))
+ continue // rest alone in the staff
// fall thru
case C.NOTE:
case C.GRACE:
break
}
@@ -3285,26 +3379,31 @@
v_s.s = s
v_s.st = s.st
v_s.end_time = s.time + s.dur
if (s.fmr) // if full meeasure rest
v_s.end_time -= s.p_v.wmeasure * .3
- if (!s.seqst) {
- s2 = s.ts_prev
- if (s2.st == s.st
- && s.ymx > s2.ymn
- && !s2.invis) {
- if (s2.type == C.NOTE) {
- v_s.d = s2.ymn - s.ymx
- break
- }
- if (s2.type == C.REST
- && s2.y < 18
- && s.y >= 6)
- v_s.d = -6
- }
- }
- continue
+ if (s.seqst)
+ continue
+ s2 = s.ts_prev
+ if (s2.st != s.st
+ || s2.invis)
+ continue
+ d = s2.ymn
+ if (v_s_tb[s2.v] && v_s_tb[s2.v].d
+ && v_s_tb[s2.v] >= s.time)
+ d += v_s_tb[s2.v].d
+ if (s.ymx <= d)
+ continue
+ if (s2.type == C.NOTE) {
+ v_s.d = d - s.ymx
+ break
+ }
+ if (s2.type == C.REST
+ && s2.y < 18
+ && s.y >= 6)
+ v_s.d = (d - s.ymx) / 2
+ break
case C.NOTE:
if (s.invis || !s.multi)
continue
break
}
@@ -3844,16 +3943,15 @@
set_clefs();
self.set_pitch(null)
}
// get the left offsets of the first and other staff systems
-// return [lsh1, lsho]
+// return left shift [no name, first music line, other lines
function get_lshift() {
var st, v, p_v, p1, po, fnt, w,
sy = cur_sy,
- lsh1 = 0,
- lsho = 0,
+ lsh = [0, 0, 0],
nv = voice_tb.length
// get the max width of a voice name/subname
function get_wx(p, wx) {
var w, j,
@@ -3881,18 +3979,18 @@
if ((p1 || po) && !fnt) {
set_font("voice")
fnt = gene.deffont
}
if (p1) {
- w = get_wx(p1, lsh1)
- if (w > lsh1)
- lsh1 = w
+ w = get_wx(p1, lsh[1])
+ if (w > lsh[1])
+ lsh[1] = w
}
if (po) {
- w = get_wx(po, lsho)
- if (w > lsho)
- lsho = w
+ w = get_wx(po, lsh[2])
+ if (w > lsh[2])
+ lsh[2] = w
}
}
// add the width of the braces/brackets
w = 0
while (sy) {
@@ -3907,39 +4005,41 @@
}
if (w == 12)
break
sy = sy.next
}
- lsh1 += w
- lsho += w
- return [lsh1, lsho]
+ lsh[0] = w
+ lsh[1] += w
+ lsh[2] += w
+ return lsh
} // get_lshift()
/* -- return the left indentation of the staves -- */
function set_indent(lsh) {
var st, v, w, p_voice, p, i, j, font,
vnt = 0,
fmt = tsnext ? tsnext.fmt : cfmt
// name or subname?
- if (fmt.systnames) { // display the names in the staff system
+ if (fmt.systvoices) { // put the voice names in the staff system
for (v = voice_tb.length; --v >= 0; ) {
p_voice = voice_tb[v]
if (!cur_sy.voices[v]
|| !gene.st_print[p_voice.st])
continue
if (p_voice.nm
- && (p_voice.new_name || fmt.systnames == 2)) {
- vnt = 2 // full name
+ && (fmt.systvoices == 1
+ || (p_voice.new_name && fmt.systvoices == 3))) {
+ vnt = 1 // full name
break
}
if (p_voice.snm)
- vnt = 1 // subname
+ vnt = 2 // subname
}
}
gene.vnt = vnt // voice name type for draw
- return vnt == 2 ? lsh[0] : lsh[1]
+ return lsh[vnt]
}
/* -- decide on beams and on stem directions -- */
/* this routine is called only once per tune */
function set_beams(sym) {
@@ -4154,10 +4254,12 @@
delete s1.notes[i1].acc
}
for (i1 = 0; i1 <= s1.nhd; i1++)
s1.notes[i1].shhd += sh2
}
+ if (s1.dots == s2.dots) // let the dots in one voice
+ s2.dots = 0
return true
}
/* handle unison with different accidentals */
function unison_acc(s1, s2, i1, i2) {
@@ -4337,10 +4439,14 @@
|| s.invis) {
if (s.type == C.STAVES)
sy = s.sy
continue
}
+
+ // set the dot vertical offset of secondary voices
+ if (s.second)
+ s.dot_low = 1 //true
/* treat the stem on two staves with different directions */
if (s.xstem
&& s.ts_prev.stem < 0) {
for (m = 0; m <= s.nhd; m++) {
@@ -4366,16 +4472,10 @@
}
if (!s2)
continue
s1 = s
- /* set the dot vertical offset */
- if (sy.voices[s1.v].range < sy.voices[s2.v].range)
- s2.dot_low = true
- else
- s1.dot_low = true
-
/* no shift if no overlap */
if (s1.ymn > s2.ymx
|| s1.ymx < s2.ymn)
continue
@@ -4509,16 +4609,13 @@
continue
}
sd = 0;
if (s1.dots) {
- if (s2.dots) {
- if (!t) /* if no dot clash */
- sd = 1 /* align the dots */
- } else {
- v_invert() // shift the first voice
- }
+ if (!s2.dots
+ || !t) // if no dot clash
+ sd = 1 // align the dots
} else if (s2.dots) {
if (d2 + dr < d + dr2)
sd = 1 /* align the dots */
}
pl = left2;
@@ -4634,12 +4731,12 @@
} else {
g.y = ymx;
g.ys = ymn - slen;
ymn = Math.round(g.ys)
}
- ymx += 4
- ymn -= 4
+ ymx += 2
+ ymn -= 2
if (ymn < ymin)
ymin = ymn
else if (ymx > ymax)
ymax = ymx;
g.ymx = ymx;
@@ -4682,11 +4779,11 @@
}
/* set height of stem end */
slen = s.fmt.stemheight
switch (nflags) {
- case 2: slen += 0; break
+// case 2: slen += 0; break
case 3: slen += 4; break
case 4: slen += 8; break
case 5: slen += 12; break
}
if ((scale = s.p_v.scale) != 1)
@@ -4698,10 +4795,23 @@
} else {
ymx = ymn
}
if (s.ntrem)
slen += 2 * s.ntrem /* tremolo */
+ if (s.decstm) { // if deco on the stem
+ if (nflags <= 0) {
+ if (slen < s.decstm + 6)
+ slen = s.decstm + 6
+ } else {
+ var t = nflags * 4 // beams
+
+ if (s.beam_st & s.beam_end)
+ t += 2 // flags
+ if (slen < s.decstm + 4 + t)
+ slen = s.decstm + 4 + t
+ }
+ }
if (s.stemless) {
if (s.stem >= 0) {
s.y = ymn;
s.ys = ymx
} else {
@@ -4885,11 +4995,11 @@
}
} // set_brace()
// set the top and bottom of the staves
function set_top_bot() {
- var st, p_staff, i, j, l
+ var st, p_staff, i, l
for (st = 0; st <= nstaff; st++) {
p_staff = staff_tb[st]
// ledger lines
@@ -4907,11 +5017,11 @@
case '-':
continue
}
break
}
- p_staff.botline = p_staff.botbar = i * 6
+ p_staff.botbar = i * 6
if (i >= l - 2) { // 0, 1 or 2 lines
if (p_staff.stafflines[i] != '.') {
p_staff.botbar -= 6;
p_staff.topbar += 6
} else { // no line: big bar
@@ -4920,27 +5030,10 @@
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((l - i + 1) * 2
- + 2) // (bug android 4.0)
- for (j = 1; i < l; i++, j += 2) {
- switch (p_staff.stafflines[i]) {
- case '|':
- case '[':
- p_staff.hlmap[j - 1] = 1; // no helper line
- p_staff.hlmap[j] = 1;
- p_staff.hlmap[j + 1] = 1
- break
- }
- }
}
} // set_top_bot()
// remove the staff system at start of line
if (tsfirst.type == C.STAVES) {
@@ -5100,11 +5193,11 @@
if (xmin > width) {
error(1, s, "Line too much shrunk $1 $2 $3",
xmin.toFixed(1),
xx.toFixed(1),
width.toFixed(1))
- break
+// break
}
if (s.space) {
if (s.space < s.shrink) {
xse += s.shrink;
xx += s.shrink
@@ -5152,10 +5245,12 @@
spf = (width - xs - xse) / (xx - xse)
} else {
spf = spf_last
if (ll && spf < s.fmt.stretchlast)
spf = s.fmt.stretchlast
+ else if (spf < 1 - cfmt.maxshrink)
+ spf = 1 - cfmt.maxshrink
if (spf > (width - xs) / xx)
spf = (width - xs) / xx
}
while (--cnt >= 0) {
xx = 0;
@@ -5272,11 +5367,11 @@
}
/* -- generate the music -- */
// (possible hook)
Abc.prototype.output_music = function() {
- var v, lwidth, indent, lsh, line_height, ts1st, tslast, p_v,
+ var v, lwidth, indent, lsh, line_height, ts1st, tslast, p_v, meter1,
nv = voice_tb.length
set_global()
if (nv > 1) // if many voices
self.set_stem_dir() // set the stems direction in 'multi'
@@ -5320,10 +5415,11 @@
// save symbol pointers for play
ts1st = tsfirst
v = nv
while (--v >= 0)
voice_tb[v].osym = voice_tb[v].sym
+ meter1 = ts1st.p_v.meter
spf_last = .65 // last spacing factor
while (1) { /* loop per music line */
set_piece();
indent = set_indent(lsh)
@@ -5378,6 +5474,7 @@
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
}
+ ts1st.p_v.meter = meter1
}
Index: core/parse.js
==================================================================
--- core/parse.js
+++ core/parse.js
@@ -308,11 +308,11 @@
// get a stafflines value
function get_st_lines(param) {
if (!param)
return
- if (/^[\]\[|.-]+$/.test(param))
+ if (/^[\]\[|.':-]+$/.test(param)) // '
return param.replace(/\]/g, '[')
var n = +param
switch (n) {
case 0: return "..."
@@ -325,23 +325,17 @@
return "||||||||||||||||".slice(0, n)
}
// create a block symbol in the tune body
function new_block(subtype) {
- var c_v,
- s = {
+ var s = {
type: C.BLOCK,
subtype: subtype,
dur: 0
}
- c_v = curvoice
- if (subtype.slice(0, 4) != "midi") // if not a play command
- curvoice = voice_tb[0] // set the block in the first voice
sym_link(s)
- if (c_v)
- curvoice = c_v
return s
}
// set the voice parameters
// (possible hook)
@@ -508,16 +502,17 @@
if (curvoice.snm[0] == '"')
curvoice.snm = curvoice.snm.slice(1, -1);
break
case "stafflines=":
val = get_st_lines(a.shift())
- if (val == undefined)
+ if (val == undefined) {
syntax(1, "Bad %%stafflines value")
- else if (curvoice.st != undefined)
+ break
+ }
+ if (curvoice.st != undefined)
par_sy.staves[curvoice.st].stafflines = val
- else
- curvoice.stafflines = val
+ curvoice.stafflines = val
break
case "staffnonote=":
val = +a.shift()
if (isNaN(val))
syntax(1, "Bad %%staffnonote value")
@@ -774,12 +769,12 @@
// 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 = new Int8Array(s.k_map)
- i = s.k_a_acc.length
+ s.k_map = Array.prototype.slice.call(s.k_map) // simple Array
+ i = s.k_a_acc.length // (for micro-accidentals)
while (--i >= 0) {
note = s.k_a_acc[i]
s.k_map[(note.pit + 19) % 7] = note.acc
}
}
@@ -906,11 +901,11 @@
if (i >= p.length
|| p[i + 1] == '(') /* "M:5 (2/4+3/4)" */
break
meter.top += p[i++]
}
- m1 = eval(meter.top)
+ m1 = eval(meter.top.replace(/ /g, '+'))
break
}
if (!in_parenth) {
if (meter.bot)
m2 = +meter.bot
@@ -1751,44 +1746,80 @@
// The global 'maps' object is indexed by the map name.
// Its content is an object ('map') indexed from the map type:
// - normal = ABC note
// - octave = 'o' + ABC note in C..B interval
// - key = 'k' + scale index
-// - all = 'all'
+// - tonic = 't' + mode index
+// - any = '*'
// The 'map' is stored in the note. It is an array of
// [0] array of heads (glyph names)
// [1] print (note)
// [2] color
// [3] play (note)
function set_map(p_v, note, acc,
- trp) { // transpose done
+ trp_p) { // flag "do transpose?"
var nn = not2abc(note.pit, acc),
- map = maps[p_v.map] // never null
-
- if (!map[nn]) {
- nn = 'o' + nn.replace(/[',]+/, '') // ' octave
- if (!map[nn]) {
- nn = 'k' + ntb[(note.pit + 75 -
- p_v.ckey.k_sf * 11) % 7]
- if (!map[nn]) {
- nn = 'all' // 'all'
- if (!map[nn])
- return
- }
- }
- }
+ map = maps[p_v.map]
+
+ if (!map)
+ return
+
+ // test if 'nn' is in the map
+ function map_p() {
+ if (map[nn])
+ return 1 //true
+ var sf, d
+
+ nn = 'o' + nn.replace(/[',]+/, '') // '
+ if (map[nn])
+ return 1 //true
+//fixme: useless
+ nn = 'k' + ['__','_','=','^','^^','='][acc + 2] // key chromatic
+ + ntb[(note.pit + 75 - p_v.ckey.k_sf * 11) % 7]
+ if (map[nn])
+ return 1 //true
+ nn = nn.replace(/[_=^]/g,'') // key diatonic
+ if (map[nn])
+ return 1 //true
+ sf = p_v.ckey.k_sf + [0, 2, 4, -1, 1, 3, -2][p_v.ckey.k_mode]
+ if (sf < -7)
+ sf += 7
+ else if (sf > 7)
+ sf -= 7
+ d = abc2svg.keys[sf + 7]
+ [(note.pit + 75) % 7]
+ nn = 't' + ['__','_','=','^','^^','='] // tonic chromatic
+ [acc - d + 2]
+ + ntb[(note.pit + 75 - p_v.ckey.k_mode
+ - p_v.ckey.k_sf * 11) % 7]
+ if (map[nn])
+ return 1 //true
+ nn = nn.replace(/[_=^]/g,'') // tonic diatonic
+ if (map[nn])
+ return 1 //true
+ nn = '*' // any note
+ return map[nn]
+ } // map_p()
+
+ if (!map_p()) // note in the map?
+ return // no
map = map[nn]
- if (!trp) { // transpose not done yet
- if (map[1]) { // if note shift
+
+ if (trp_p) {
+ if (map[1] && map[1].notrp)
+ note.notrp = 1 //true // no transpose
+ return
+ }
+
+ if (map[1] // if note transpose
+ && !note.map) { // for the first time
note.pit = map[1].pit
note.acc = map[1].acc
if (map[1].notrp) {
note.notrp = 1 //true // no transpose
note.noplay = 1 //true // no play
}
- }
- return
}
note.map = map
if (map[2]) // if color
note.color = map[2]
@@ -2001,12 +2032,13 @@
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,
+ var note, s, in_chord, c, tie_s, acc_tie,
+ i, n, s2, nd, res, num, apit, div, ty,
+ chdur = 1,
dpit = 0,
sl1 = [],
line = parse.line,
a_dcn_sav = a_dcn // save parsed decoration names
@@ -2052,17 +2084,10 @@
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
- }
-
// convert 'Z'/'Z1' to a whole measure rest
if (s.nmes == 1) {
s.type = C.REST;
s.dur_orig = s.dur;
s.fmr = 1 // full measure rest
@@ -2110,10 +2135,21 @@
}]
break
case '[': // chord
in_chord = true;
c = line.next_char()
+ i = line.buffer.indexOf(']', line.index)
+ if (i < 0) {
+ syntax(1, "No end of chord")
+ return
+ }
+ n = line.index // save the parser index
+ line.index = i + 1 // set the parser to the end of chord
+ nd = parse_dur(line)
+ chdur = nd[0] / nd[1] // length factor of the chord
+ in_chord = reg_dur.lastIndex // hack: index after the chord length
+ line.index = n // restore the parser index
// fall thru
default: // accidental, chord, note
if (curvoice.acc_tie) {
acc_tie = curvoice.acc_tie
curvoice.acc_tie = null
@@ -2131,19 +2167,19 @@
i = c.charCodeAt(0);
if (i >= 128) {
syntax(1, errs.not_ascii)
return //null
}
- type = char_tb[i]
- switch (type[0]) {
+ ty = char_tb[i]
+ switch (ty[0]) {
case '(':
sl1.push(parse_vpos());
c = line.char()
continue
case '!':
- if (type.length > 1)
- a_dcn.push(type.slice(1, -1))
+ if (ty.length > 1)
+ a_dcn.push(ty.slice(1, -1))
else
get_deco() // line -> a_dcn
c = line.next_char()
continue
}
@@ -2156,10 +2192,11 @@
C.BLEN :
curvoice.ulen)
if (!note)
return //null
+ note.dur *= chdur // chord factor
if (curvoice.octave)
note.pit += curvoice.octave * 7
// get the real accidental
apit = note.pit + 19 // pitch from C-1
@@ -2171,11 +2208,12 @@
i = curvoice.acc[apit]
if (!i)
i = curvoice.ckey.k_map[apit % 7] || 0
}
- if (i) {
+ if (i
+ && !curvoice.ckey.k_drum) {
if (cfmt["propagate-accidentals"][0] == 'p')
curvoice.acc[apit % 7] = i
else if (cfmt["propagate-accidentals"][0] != 'n')
curvoice.acc[apit] = i
}
@@ -2186,15 +2224,13 @@
// set the MIDI pitch
if (!note.midi) // if not map play
note.midi = pit2mid(apit, i)
// transpose
- if (curvoice.map
- && maps[curvoice.map])
- set_map(curvoice, note, i) // transpose not done
if (curvoice.tr_sco) {
- if (!note.notrp) {
+ set_map(curvoice, note, i, 1) // possible transpose?
+ if (!note.notrp) { // yes
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
@@ -2203,10 +2239,12 @@
dpit = note.pit + 19 - apit
}
}
if (curvoice.tr_snd)
note.midi += curvoice.tr_snd
+ if (curvoice.map)
+ set_map(curvoice, note, i)
//fixme: does not work if transposition
if (i) {
switch (cfmt["writeout-accidentals"][1]) {
case 'd': // added
@@ -2238,14 +2276,10 @@
ss: s,
nts: note // starting note
})
}
}
- 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
@@ -2284,20 +2318,18 @@
syntax(1, "Misplaced dot")
break
}
break
}
+ if (a_dcn.length) {
+ s.time = curvoice.time // (needed for !tie)!
+ dh_cnv(s, note)
+ }
+
if (c == ']') {
- line.index++;
-
- // adjust the chord duration
- nd = parse_dur(line);
+ line.index = in_chord
s.nhd = s.notes.length - 1
- for (i = 0; i <= s.nhd ; i++) {
- note = s.notes[i];
- note.dur = note.dur * nd[0] / nd[1]
- }
break
}
}
// handle the starting slurs
@@ -2326,10 +2358,21 @@
syntax(1, errs.bad_grace)
return //null
}
if (s.notes) { // if note or rest
+ if (!s.fmr) { // (but not full measure rest)
+ n = s.dur_orig / 12 // check its duration
+ i = 0
+ while (!(n & 1)) {
+ n >>= 1
+ i++
+ }
+ if ((n + 1) & n)
+ error(0, s, "Non standard note duration $1",
+ n + '/' + (1 << (6 - i)))
+ }
if (!grace) {
switch (curvoice.pos.stm & 0x07) {
case C.SL_ABOVE: s.stem = 1; break
case C.SL_BELOW: s.stem = -1; break
case C.SL_HIDDEN: s.stemless = true; break
Index: core/subs.js
==================================================================
--- core/subs.js
+++ core/subs.js
@@ -1,8 +1,8 @@
// abc2svg - subs.js - text output
//
-// Copyright (C) 2014-2023 Jean-Francois Moine
+// Copyright (C) 2014-2024 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
@@ -17,27 +17,21 @@
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core. If not, see .
// add font styles
var sheet
-var add_fstyle = typeof document != "undefined" ?
- function(s) {
+var add_fstyle = abc2svg.el
+ ? function(s) {
var e
- if (cfmt.fullsvg)
- font_style += "\n" + s
- if (!sheet) {
- if (abc2svg.sheet) { // if styles from a previous generation
- sheet = abc2svg.sheet
- e = sheet.cssRules.length
- while (--e >= 0)
- sheet.deleteRule(e)
- } else {
- e = document.createElement('style')
- document.head.appendChild(e)
- abc2svg.sheet = sheet = e.sheet
- }
- }
+
+ font_style += "\n" + s
+ if (!abc2svg.styles) {
+ e = document.createElement('style')
+ document.head.appendChild(e)
+ abc2svg.styles = e
+ }
+ sheet = abc2svg.styles.sheet
s = s.match(/[^{]+{[^}]+}/g) // insert each style
while (1) {
e = s.shift()
if (!e)
break
@@ -118,60 +112,56 @@
}
// return the character width with the current font
function cwidf(c) {
return cwid(c) * gene.curfont.swfac
}
+
+// make XML clean
+function clean_txt(p) {
+ return p.replace(/<|>|&[^&\s]*?;|&/g, function(c) {
+ switch (c) {
+ case '<': return "<"
+ case '>': return ">"
+ case '&': return "&"
+ }
+ return c // &xxx;
+ })
+} // clean_txt()
// estimate the width and height of a string ..
var strwh
(function() {
- if (typeof document != "undefined") {
+ if (typeof document != "undefined"
+ && abc2svg.el) {
// .. by the browser
- // create a text element if not done yet
- var el
-
// 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'
- el.style.visibility = "hidden"
- document.body.appendChild(el)
- }
var c,
+ el = abc2svg.el, // hidden created by edit/abcweb/...
font = gene.curfont,
h = font.size,
w = 0,
n = str.length,
i0 = 0,
i = 0
+ if (!el.parentElement) // insert back the in the document
+ document.body.appendChild(el)
el.className = font_class(font)
- el.style.lineHeight = 1
if (typeof str == "object") { // if string already converted
el.innerHTML = str
str.wh = [ el.clientWidth, el.clientHeight ]
return str.wh
}
-
- str = str.replace(/<|>|&[^&\s]*?;|&/g, function(c){
- switch (c) {
- case '<': return "<"
- case '>': return ">"
- case '&': return "&"
- }
- return c // &xxx;
- })
+ str = clean_txt(str)
while (1) {
i = str.indexOf('$', i)
if (i >= 0) {
c = str[i + 1]
@@ -181,20 +171,21 @@
font = get_font("u" + c)
} else {
i++
continue
}
+ el.className = font_class(font)
}
el.innerHTML = str.slice(i0, i >= 0 ? i : undefined)
w += el.clientWidth
//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
}
return [w, h]
}
@@ -303,11 +294,11 @@
if (c_font != o_font)
o += ""
// convert to String and memorize the string width and height
o = new String(o)
- if (typeof document != "undefined")
+ if (abc2svg.el)
strwh(o) // browser
else
o.wh = strwh(str) // CLI
gene.curfont = c_font // keep the current font for the next paragraph
@@ -338,11 +329,11 @@
action, // default: align left
w, // needed for justify
wh) { // optional [width, height]
if (!wh)
wh = str.wh || strwh(str)
- if (cfmt.trimsvg) {
+ if (cfmt.singleline || cfmt.trimsvg) {
var wx = wh[0]
switch (action) {
case 'c':
wx = wh[0] / 2
break
@@ -526,11 +517,12 @@
w += ww[0]
if (w >= strlw) {
vskip(wh * cfmt.lineskipfac)
xy_str(0, ww[1] * .2,
words.slice(k, j).join(' '),
- action, strlw)
+ action, strlw,
+ [w - ww[0], ww[1]])
k = j;
w = ww[0]
wh = 0
}
if (ww[1] > wh)
@@ -593,14 +585,10 @@
xy_str(x, y, p.slice(0, k), 'r')
if (i < p.length)
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
words = words.split('\n')
nw = words.length
for (i = 0; i < nw; i++) {
p = words[i]
@@ -611,10 +599,16 @@
} else if (p.length > maxn) {
maxn = p.length
i1 = i // keep this line
}
}
+ if (i1 == undefined)
+ return // no text in the W: lines!
+
+ set_font("words")
+ vskip(cfmt.wordsspace)
+ svg_flush()
w = get_lwidth() / 2 // half line width
lw = strwh(words[i1])[0]
i1 = i2 = 0
if (lw < w) { // if 2 columns
@@ -733,11 +727,11 @@
R: "info",
T: "title",
X: "title"
}
function write_headform(lwidth) {
- var c, font, font_name, align, x, y, sz, w,
+ var c, font, font_name, align, x, y, sz, w, yd,
info_val = {},
info_font = Object.create(info_font_init),
info_sz = {
A: cfmt.infospace,
C: cfmt.composerspace,
@@ -880,10 +874,11 @@
if (info_sz[c])
sz += info_sz[c];
set_font(font);
x = xa[align];
y = ya[align] + sz
+ yd = y - font.size * .22 // descent
if (c == 'Q') { /* special case for tempo */
self.set_width(glovar.tempo)
if (!glovar.tempo.invis) {
if (align != 'l') {
@@ -898,11 +893,11 @@
}
} else if (str) {
if (c == 'T')
str = trim_title(str,
info_font.T[0] == 's')
- xy_str(x, -y, str, align)
+ xy_str(x, -yd, str, align)
}
if (c == 'T') {
font_name = info_font.T = "subtitle";
info_sz.T = cfmt.subtitlespace
@@ -916,11 +911,11 @@
set_font(font)
}
while (info_val[c].length > 0) {
y += sz;
str = info_val[c].shift();
- xy_str(x, -y, str, align)
+ xy_str(x, -yd, str, align)
}
}
info_nb[c]--;
ya[align] = y
}
@@ -932,15 +927,46 @@
break
ya.c = ya.r = ya.l
}
vskip(ya.l)
}
+
+// get the meaningful names of a part (P:)
+function partname(c) {
+ var i,
+ tmp = cfmt.partname.split('\n')
+
+ for (i = 0; i < tmp.length; i++) {
+ if (tmp[i][0] == c) {
+ c = tmp[i].match(/.\s+(\w+)\s*(.+)?/)
+ if (!c[2])
+ c[2] = c[1]
+ if (c[2][0] == '"')
+ c[2] = c[2].slice(1, -1)
+ return c
+ }
+ }
+ return [0, c, c]
+} // partname()
/* -- output the tune heading -- */
function write_heading() {
- var i, j, area, composer, origin, rhythm, down1, down2,
+ var i, j, area, composer, origin, rhythm, down1, down2, p,
lwidth = get_lwidth()
+
+ // build a new sequence of the parts with clearer names
+ function new_part() {
+ var i,
+ o = ""
+
+ for (i = 0; i < info.P.length; i++) {
+ if (i)
+ o += ' '
+ o += partname(info.P[i])[1]
+ }
+ return o
+ } // new_part()
vskip(cfmt.topspace)
if (cfmt.titleformat) {
write_headform(lwidth);
@@ -1034,12 +1060,15 @@
i = cfmt.partsspace + gene.curfont.size + gene.curfont.pad
if (down1 + i > down2)
down2 = down1 + i
else
down2 += i
- xy_str(0, -down2 + gene.curfont.size *.22, info.P)
+ p = info.P
+ if (cfmt.partname)
+ p = new_part()
+ xy_str(0, -down2 + gene.curfont.size *.22, p)
down2 += gene.curfont.pad
} else if (down1 > down2) {
down2 = down1
}
vskip(down2 + cfmt.musicspace)
}
Index: core/svg.js
==================================================================
--- core/svg.js
+++ core/svg.js
@@ -1,8 +1,8 @@
// abc2svg - svg.js - svg functions
//
-// Copyright (C) 2014-2023 Jean-Francois Moine
+// Copyright (C) 2014-2024 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
@@ -22,10 +22,12 @@
\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.sltnW{stroke:currentColor;fill:none;stroke-width:.25}\
+\n.sldW{stroke:currentColor;fill:none;stroke-width:.7;stroke-dasharray:5,10}\
\n.sW{stroke:currentColor;fill:none;stroke-width:.7}\
\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
@@ -39,10 +41,12 @@
defined_glyph = {},
defs = '',
fulldefs = '', // unreferenced defs as
stv_g = { /* staff/voice graphic parameters */
scale: 1,
+ stsc: 1, // staff scale
+ vsc: 1, // voice scale
dy: 0,
st: -1,
v: -1,
g: 0
// color: undefined
@@ -57,14 +61,15 @@
var tgls = {
"mtr ": {x:0, y:0, c:"\u0020"}, // space
brace: {x:0, y:0, c:"\ue000"},
lphr: {x:0, y:23, c:"\ue030"},
mphr: {x:0, y:23, c:"\ue038"},
- sphr: {x:0, y:26, c:"\ue039"},
+ sphr: {x:0, y:25, c:"\ue039"},
short: {x:0, y:32, c:"\ue038"},
- tick: {x:0, y:29, c:"\ue039"},
- rdots: {x:-1, y:0, c:"\ue043"}, // repeat dots
+ tick: {x:0, y:25, c:"\ue039"},
+ rdots: {x:0, y:0, c:"\ue043"}, // repeat dots
+ rdot: {x:0, y:0, c:"\ue044"}, // single repeat dot
dsgn: {x:-12, y:0, c:"\ue045"}, // D.S.
dcap: {x:-12, y:0, c:"\ue046"}, // D.C.
sgno: {x:-5, y:0, c:"\ue047"}, // segno
coda: {x:-10, y:0, c:"\ue048"},
tclef: {x:-8, y:0, c:"\ue050"},
@@ -168,11 +173,11 @@
turn: {x:-5, y:0, c:"\ue567"},
turnx: {x:-5, y:0, c:"\ue569"},
umrd: {x:-6, y:2, c:"\ue56c"},
lmrd: {x:-6, y:2, c:"\ue56d"},
dplus: {x:-3, y:0, c:"\ue582"}, // plus
- sld: {x:-8, y:12, c:"\ue5d0"}, // slide
+ sld: {x:-8, y:2, c:"\ue5d0"}, // slide
grm: {x:-3, y:-2, c:"\ue5e2"}, // grace mark
dnb: {x:-3, y:0, c:"\ue610"}, // down bow
upb: {x:-2, y:0, c:"\ue612"}, // up bow
opend: {x:-2, y:-2, c:"\ue614"}, // harmonic
roll: {x:0, y:0, c:"\ue618"},
@@ -328,44 +333,44 @@
/* -- set the staff scale (only) -- */
function set_sscale(st) {
var new_scale, dy
if (st != stv_g.st && stv_g.scale != 1)
- stv_g.scale = 0;
+ stv_g.scale = 1
new_scale = st >= 0 ? staff_tb[st].staffscale : 1
if (st >= 0 && new_scale != 1)
dy = staff_tb[st].y
else
dy = posy
- if (new_scale == stv_g.scale && dy == stv_g.dy)
+ if (new_scale == stv_g.scale && dy == stv_g.dy
+ && stv_g.st == st && stv_g.vsc == 1)
return
- stv_g.scale = new_scale;
+ stv_g.stsc =
+ stv_g.scale = new_scale
+ stv_g.vsc = 1
stv_g.dy = dy;
stv_g.st = st;
stv_g.v = -1;
set_g()
}
/* -- set the voice or staff scale -- */
function set_scale(s) {
- var new_dy,
+ var new_dy = posy,
+ st = staff_tb[s.st].staffscale == 1 ? -1 : s.st,
new_scale = s.p_v.scale
- if (new_scale == 1) {
- set_sscale(s.st)
- return
- }
- new_dy = posy
- if (staff_tb[s.st].staffscale != 1) {
- new_scale *= staff_tb[s.st].staffscale;
- new_dy = staff_tb[s.st].y
- }
- if (new_scale == stv_g.scale && stv_g.dy == posy)
+ if (st >= 0) {
+ new_scale *= staff_tb[st].staffscale
+ new_dy = staff_tb[st].y
+ }
+ if (new_scale == stv_g.scale && stv_g.dy == new_dy)
return
stv_g.scale = new_scale;
+ stv_g.vsc = s.p_v.scale
stv_g.dy = new_dy;
- stv_g.st = staff_tb[s.st].staffscale == 1 ? -1 : s.st;
+ stv_g.st = st
stv_g.v = s.v;
set_g()
}
// -- set the staff output buffer and scale when delayed output
@@ -529,11 +534,11 @@
if (stv_g.g)
return -y
if (stv_g.scale == 1)
return posy - y
if (stv_g.v >= 0)
- return (stv_g.dy - y) / voice_tb[stv_g.v].scale
+ return (stv_g.dy - y) / stv_g.vsc
return stv_g.dy - y // staff scale only
}
Abc.prototype.sy = sy;
Abc.prototype.sh = function(h) {
if (stv_g.st < 0)
@@ -682,11 +687,11 @@
// out_XYAB('A\n', x, y, tgl.c)
gla[0].push(sx(x))
gla[1].push(sy(y))
gla[2] += tgl.c
}
- } else {
+ } else if (gl != 'nil') {
error(1, null, 'no definition of $1', gl)
}
}
}
// - specific functions -
@@ -911,11 +916,11 @@
var name = de.dd.glyph // class
if (name == 'fng') {
out_XYAB('\
A\n',
- x - 2, y, m_gl(de.dd.str))
+ x - 2, y + 1, m_gl(de.dd.str))
return
}
if (name == '@') { // compatibility
name = 'at'
@@ -1190,12 +1195,12 @@
de1.x -= 8 // (there is no note width)
}
deco_l_tb[name](x, y, de)
}
-// return a tempo note
-function tempo_note(s, dur) {
+// add a tempo note in 'str' and return its number of characters
+function tempo_note(str, s, dur, dy) {
var p,
elts = identify_note(s, dur)
switch (elts[0]) { // head
case C.OVAL:
@@ -1216,13 +1221,18 @@
p = "\ueca5"
break
}
break
}
- if (elts[1]) // dot
- p += '\uecb7'
- return p
+ str.push('' +
+ p + ''
+ + (elts[1] ? '\u2009.' : '')) // dot
+ return elts[1] ? 2 : 1
} // tempo_note()
// build the tempo string
function tempo_build(s) {
var i, j, bx, p, wh, dy,
@@ -1242,18 +1252,11 @@
w += strwh(s.tempo_str1)[0]
}
if (s.tempo_notes) {
dy = ' dy="-1"' // notes a bit higher
for (i = 0; i < s.tempo_notes.length; i++) {
- p = tempo_note(s, s.tempo_notes[i])
- str.push('' +
- p + '')
- j = p.length > 1 ? 2 : 1 // (note and optional dot)
+ j = tempo_note(str, s, s.tempo_notes[i], dy)
w += j * gene.curfont.swfac
dy = ''
}
str.push('=')
w += cwidf('=')
@@ -1264,18 +1267,11 @@
}
if (s.tempo) { // with a number of beats per minute
str.push(s.tempo)
w += strwh(s.tempo.toString())[0]
} else { // with a beat as a note
- p = tempo_note(s, s.new_beat)
- str.push('' +
- p + '')
- j = p.length > 1 ? 2 : 1
+ j = tempo_note(str, s, s.new_beat, ' dy="-1"')
w += j * gene.curfont.swfac
dy = 'y'
}
}
if (s.tempo_str2) {
@@ -1325,14 +1321,28 @@
// update the vertical offset
function vskip(h) {
posy += h
}
+
+// clear the styles
+function clr_sty() {
+ font_style = ''
+ if (cfmt.fullsvg) {
+ defined_glyph = {}
+ for (var i = 0; i < abc2svg.font_tb.length; i++)
+ abc2svg.font_tb[i].used = 0 //false
+ ff.used = 0 //false // clear the font-face
+ } else {
+ style =
+ fulldefs = ''
+ }
+} // clr_sty()
// create the SVG image of the block
function svg_flush() {
- if (multicol || !output || !user.img_out || posy == 0)
+ if (multicol || !user.img_out || posy == 0)
return
var i, font,
fmt = tsnext ? tsnext.fmt : cfmt,
w = Math.ceil((fmt.trimsvg || fmt.singleline == 1)
@@ -1343,10 +1353,12 @@
fill="currentColor" stroke-width=".7"',
g = ''
glout()
+ if (cfmt.fgcolor)
+ head += ' color="' + cfmt.fgcolor + '"'
font = get_font("music")
head += ' class="' + font_class(font) +
' tune' + tunes.length + '"\n' // tune index for play
posy *= cfmt.scale
@@ -1354,19 +1366,15 @@
head += user.imagesize
else
head += ' width="' + w
+ 'px" height="' + posy.toFixed(0) + 'px"'
head += ' viewBox="0 0 ' + w + ' '
- + posy.toFixed(0) + '"'
- if (cfmt.fgcolor || cfmt.bgcolor)
- head += ' style="'
- + (cfmt.fgcolor ? ('color:' + cfmt.fgcolor + ';')
- : '')
- + (cfmt.bgcolor ? ('background-color:' + cfmt.bgcolor)
- : '')
- + '"'
- head += '>\n' + fulldefs
+ + posy.toFixed(0) + '">\n'
+ head += fulldefs
+ if (cfmt.bgcolor)
+ head += '\n'
if (style || font_style)
head += '\n'
if (defs)
@@ -1384,37 +1392,36 @@
if (psvg) // if PostScript support
psvg.ps_flush(true); // + setg(0)
// start a block if needed
+ if (parse.state == 1 && user.page_format && !blkdiv)
+ blkdiv = 1 // new tune
if (blkdiv > 0) {
user.img_out(blkdiv == 1 ?
'
' :
'
')
blkdiv = -1 // block started
+ } else if (blkdiv < 0 && cfmt.splittune) {
+ i = 1 // header and first music line
+ blkdiv = 0
}
user.img_out(head + output + g + "");
+ if (i)
+ user.img_out("
")
output = ""
- font_style = ''
- if (cfmt.fullsvg) {
- defined_glyph = {}
- for (i = 0; i < abc2svg.font_tb.length; i++)
- abc2svg.font_tb[i].used = false
- } else {
- style = '';
- fulldefs = ''
- }
+ clr_sty()
defs = '';
posy = 0
img.wx = 0 // space used between the margins
}
// mark the end of a
block
function blk_flush() {
svg_flush()
- if (blkdiv < 0 && (!parse.state || cfmt.splittune)) {
+ if (blkdiv < 0 && !parse.state) {
user.img_out('
')
blkdiv = 0
}
}
Abc.prototype.blk_flush = blk_flush
Index: core/tail.js
==================================================================
--- core/tail.js
+++ core/tail.js
@@ -24,10 +24,11 @@
Abc.prototype.a_de = function() { return a_de }
Abc.prototype.add_style = function(s) { style += s };
Abc.prototype.anno_a = anno_a
Abc.prototype.cfmt = function() { return cfmt };
Abc.prototype.clone = clone;
+Abc.prototype.clr_sty = clr_sty
Abc.prototype.deco_put = function(nm, s) {
a_dcn.push(nm)
deco_cnv(s)
}
Abc.prototype.defs_add = defs_add
@@ -73,14 +74,16 @@
Abc.prototype.param_set_font = param_set_font;
Abc.prototype.parse = parse;
Abc.prototype.psdeco = empty_function;
Abc.prototype.psxygl = empty_function;
Abc.prototype.set_cur_sy = function(sy) { cur_sy = sy };
+Abc.prototype.set_curvoice = function(p_v) { curvoice = p_v }
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_map = set_map
Abc.prototype.set_page = set_page
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
Index: core/tune.js
==================================================================
--- core/tune.js
+++ core/tune.js
@@ -176,10 +176,11 @@
// if the previous symbol is a grace note at the same offset as the bar
// remove the grace notes from the previous time sequence
if (!fl) {
while (prev.type == C.GRACE
&& vtb[prev.v] && !vtb[prev.v].bar_type) {
+ delete prev.seqst
vtb[prev.v] = prev
prev = prev.ts_prev
fl = 1 //true
}
}
@@ -432,20 +433,10 @@
}
}
}
} // ins_pq()
- // do the note mapping stuff
- function do_map(s) {
- var m, nt
-
- for (m = 0; m <= s.nhd; m++) {
- nt = s.notes[m]
- set_map(s.p_v, nt, nt.acc, 1) // transpose done
- }
- } // do_map()
-
// set the duration of the notes under a feathered beam
function set_feathered_beam(s1) {
var s, s2, t, d, b, i, a,
d = s1.dur,
n = 1
@@ -570,13 +561,10 @@
}
continue
case C.NOTE:
if (s.feathered_beam)
set_feathered_beam(s)
- if (p_voice.map
- && maps[p_voice.map])
- do_map(s)
break
}
}
}
}
@@ -762,13 +750,13 @@
// convert a note to ABC
function not2abc(pit, acc) {
var i,
nn = ''
- if (acc && acc != 3) {
+ if (acc) {
if (typeof acc != "object") {
- nn = ['__', '_', '', '^', '^^'][acc + 2]
+ nn = ['__', '_', '', '^', '^^', '='][acc + 2]
} else {
i = acc[0]
if (i > 0) {
nn += '^'
} else {
@@ -799,20 +787,16 @@
if (a.length < 3) {
syntax(1, errs.not_enough_p)
return
}
ns = a[1]
- if (ns[0] == '*' || ns.indexOf("all") == 0) {
- ns = 'all'
- } else {
- if (ns.indexOf("octave,") == 0 // remove the octave part
- || ns.indexOf("key,") == 0) {
+ if (ns != '*') {
+ if (ns.indexOf("octave,") == 0 // remove the octave part
+ || ns.indexOf("key,") == 0
+ || !ns.indexOf("tonic,")) {
ty = ns[0]
- ns = ns.split(',')[1]
- ns = ns.replace(/[,']+/, '').toUpperCase() //'
- if (ns.indexOf("key,") == 0)
- ns = ns.replace(/[=^_]+/, '')
+ ns = ns.split(',')[1].toUpperCase()
}
tmp = new scanBuf
tmp.buffer = ns
note = parse_acc_pit(tmp)
if (!note) {
@@ -1283,11 +1267,21 @@
j = text.indexOf('', i)
if (j < 0) {
syntax(1, "No in %%beginsvg sequence")
break
}
- style += text.slice(i + 1, j).replace(/\s+$/, '')
+ s = text.slice(i + 1, j).replace(/\s+$/gm, '')
+ if (cfmt.fullsvg) {
+ i = s.match(/@font-face[^}]*}/)
+ if (i && i[0].indexOf("text") > 0) {
+ ff.text = "\n"
+ + i[0] // assume only one @font-face
+ s = s.replace(i[0], '')
+ }
+ }
+ if (s && s != "\n")
+ style += s
}
j = 0
while (1) {
i = text.indexOf('\n', j)
if (i < 0)
@@ -1499,21 +1493,21 @@
curvoice = p_v_sav
} // fill_mr_ba()
/* -- get staves definition (%%staves / %%score) -- */
function get_staves(cmd, parm) {
- var s, p_voice, p_voice2, i, flags, v, vid, a_vf,
+ var s, p_voice, p_voice2, i, flags, v, vid, a_vf, eoln,
st, range,
nv = voice_tb.length,
maxtime = 0
// if sequence with many voices, load the other voices
if (curvoice && curvoice.clone) {
- i = parse.eol
- parse.eol = parse.bol // remove the %%staves line
+// i = parse.eol
+// parse.eol = parse.bol // remove the %%staves line
do_cloning()
- parse.eol = i
+// parse.eol = i
}
if (parm) {
a_vf = parse_staves(parm) // => array of [vid, flags]
if (!a_vf)
@@ -1524,48 +1518,60 @@
}
/* create a new staff system */
for (v = 0; v < nv; v++) {
p_voice = voice_tb[v]
+ if (p_voice.eoln) {
+ eoln = 1
+ delete p_voice.eoln
+ }
if (p_voice.time > maxtime)
maxtime = p_voice.time
}
if (!maxtime) { // if first %%staves
par_sy.staves = []
par_sy.voices = []
} else {
// if (nv) // if many voices
self.voice_adj(1)
+
+ // synchronize the voices
+ for (v = 0; v < nv; v++) {
+ p_voice = voice_tb[v]
+ p_voice.time = maxtime
+ p_voice.lyric_restart = p_voice.last_sym
+ p_voice.sym_restart = p_voice.last_sym
+ }
+
/*
* create a new staff system and
* link the 'staves' symbol in a voice which is seen from
* the previous system - see sort_all
*/
+ if (!par_sy.voices[curvoice.v])
for (v = 0; v < par_sy.voices.length; v++) {
if (par_sy.voices[v]) {
curvoice = voice_tb[v]
break
}
}
- curvoice.time = maxtime;
+
+ curvoice.eoln = eoln
s = {
type: C.STAVES,
dur: 0
}
sym_link(s); // link the staves in this voice
par_sy.nstaff = nstaff;
// if no parameter, duplicate the current staff system
- // and do a voice re-synchronization
if (!parm) {
- s.sy = clone(par_sy, 1)
+ s.sy = clone(par_sy, 2) // clone the staves and voices
par_sy.next = s.sy
par_sy = s.sy
staves_found = maxtime
- for (v = 0; v < nv; v++)
- voice_tb[v].time = maxtime
curvoice = voice_tb[par_sy.top_voice]
return
}
new_syst();
@@ -1591,11 +1597,10 @@
}
range = 0
for (i = 0; i < a_vf.length; i++) {
vid = a_vf[i][0];
p_voice = new_voice(vid);
- p_voice.time = maxtime;
v = p_voice.v
a_vf[i][0] = p_voice;
// set the range and add the overlay voices
@@ -1661,14 +1666,14 @@
p_voice.second = true
} else {
st++;
if (!par_sy.staves[st]) {
par_sy.staves[st] = {
- stafflines: p_voice.stafflines || "|||||",
staffscale: 1
}
}
+ par_sy.staves[st].stafflines = p_voice.stafflines || "|||||",
par_sy.staves[st].flags = 0
}
v = p_voice.v;
p_voice.st = p_voice.cst =
par_sy.voices[v].st = st;
@@ -2060,11 +2065,11 @@
}
p_voice = {
v: v,
id: id,
- time: 0,
+ time: staves_found >= 0 ? staves_found : 0,
new: true,
pos: {
// dyn: 0,
// gch: 0,
// gst: 0,
@@ -2132,11 +2137,11 @@
var i,
clone = curvoice.clone,
vs = clone.vs,
a = clone.a,
bol = clone.bol,
- eol = parse.eol,
+ eol = parse.bol,
parse_sav = parse,
file = parse.file
delete curvoice.clone
@@ -2243,10 +2248,12 @@
function goto_tune() {
var v, p_voice
set_page();
write_heading();
+ blk_flush() // tune heading in a specific SVG
+
if (glovar.new_nbar) {
gene.nbar = glovar.new_nbar // measure numbering
glovar.new_nbar = 0
} else {
gene.nbar = 1
Index: editor/edit.js
==================================================================
--- editor/edit.js
+++ editor/edit.js
@@ -1,8 +1,8 @@
// edit.js - file used in the abc2svg editor
//
-// Copyright (C) 2014-2023 Jean-Francois Moine
+// Copyright (C) 2014-2024 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
@@ -43,10 +43,11 @@
ctxMenu, // context menu for play
elt_ref = {}, // pointers to page HTML elements
selx = [0, 0], // selected source indexes
selx_sav = [], // (saved while playing/printing)
play = {}, // play data
+ tunes, // tune list, updated on rendering refresh
pop, // current popup message
texts = {}, // language specific texts
chg = 0, // > 0 when the textarea is modified
jsdir = document.currentScript ?
document.currentScript.src.match(/.*\//) :
@@ -159,14 +160,12 @@
}
// load the (ABC source or include) file in the textarea
function loadtune() {
var files = document.getElementById("abcfile").files
-// if (!files.length) {
-// alert('Please select a file!')
-// return
-// }
+ if (!files.length)
+ return
abc_fname[srcidx] = files[0].name
var reader = new FileReader();
// Closure to capture the file information
@@ -536,12 +535,11 @@
// 3: Continue
function play_tune(what) {
if (!abc)
return // no generation yet
var i, si, ei, elt,
- C = abc2svg.C,
- tunes = abc.tunes
+ C = abc2svg.C
if (play.playing) {
if (!play.stop) {
play.stop = -1;
play.abcplay.stop()
@@ -659,17 +657,16 @@
// play tune()
ctxMenu.style.display = "none"; // remove the play menu
play.playing = true;
- if (tunes.length) { // if new display
+ if (tunes != abc.tunes) { // if new display
+ tunes = abc.tunes
// generate the play data of all tunes
- while (1) {
- elt = tunes.shift()
- if (!elt)
- break
+ for (i = 0; i < tunes.length; i++) {
+ elt = tunes[i]
play.abcplay.add(elt[0], elt[1], elt[3])
}
play.si = play.ei = null
play.stop = 0
@@ -697,17 +694,24 @@
} else if (what != 0 && selx[1]) { // if selection without start
si = gsot(selx[1])
ei = get_ee(selx[1])
} else { // no selection => tune
elt = play.click.svg // (dummy)
- si = elt.getElementsByClassName('abcr') // symbols in the SVG
- if (!si.length) {
- play.playing = false
- return // not in a tune!
+ si = elt.getAttribute('class')
+ si = /tune(\d*)/.exec(si)
+ if (!si) {
+ play.playing = 0 //false
+ return // no tune here
}
- i = Number(si[0].getAttribute('class').slice(6, -1))
- si = gsot(i)
+ si = tunes[si[1]][0] // first symbol of the tune
+ ei = si
+ while (ei && !ei.dur)
+ ei = ei.ts_next
+ if (!ei) {
+ play.playing = 0 //false
+ return // nothing to play
+ }
ei = null
}
if (what != 3) { // if not continue
play.si = si;
@@ -868,10 +872,20 @@
// e.onauxclick = show_menu // right or middle buttons (KO in Safari)
e.ondblclick = show_menu // double click
e.oncontextmenu = show_menu
}
set_pref() // set the preferences from local storage
+
+ // create a hidden span for string width computation
+ e = document.createElement("span")
+ e.style.position = "absolute"
+ e.style.top =
+ e.style.padding = 0
+ e.style.visibility = "hidden"
+ e.style.lineHeight = 1
+ document.body.appendChild(e)
+ abc2svg.el = e
}
// drag and drop
function drag_enter(evt) {
evt.stopImmediatePropagation();
Index: font.js
==================================================================
--- font.js
+++ font.js
@@ -1,399 +1,388 @@
// abc2svg music font
var musicfont = 'url("data:application/octet-stream;base64,\
-AAEAAAAOAIAAAwBgRkZUTZHVOuoAAFgMAAAAHEdERUYAFQAUAABX8AAAABxPUy8yWMFdCgAAAWgA\
-AABWY21hcHlUuFMAAAQIAAAD5mN2dCAAIgKIAAAH8AAAAARnYXNw//8AAwAAV+gAAAAIZ2x5ZqUq\
-QgUAAAkcAABF/GhlYWQVmDJzAAAA7AAAADZoaGVhCWn/GwAAASQAAAAkaG10eNm9+0EAAAHAAAAC\
-SGxvY2HHqraAAAAH9AAAASZtYXhwANkBEgAAAUgAAAAgbmFtZeq7sB0AAE8YAAADFXBvc3SIfxKM\
-AABSMAAABbUAAQAAAAEAANGbj/xfDzz1AAsEAAAAAADRlyIXAAAAAOBezej/OPzvBUsEiAAAAAgA\
-AgAAAAAAAAABAAAEiPzvAFwEJf84/XQFSwABAAAAAAAAAAAAAAAAAAAAkgABAAAAkgDhAAUAAAAA\
-AAIAAAABAAEAAABAAC4AAAAAAAEBgAGQAAUACAKZAswAAACPApkCzAAAAesAMwEJAAACAAUDAAAA\
+AAEAAAAOAIAAAwBgRkZUTZUzWF8AAFWEAAAAHEdERUYAFQAUAABVaAAAABxPUy8yWLldBQAAAWgA\
+AABWY21hcHhDtwwAAAP4AAAD3mN2dCAAIgKIAAAH2AAAAARnYXNw//8AAwAAVWAAAAAIZ2x5Znhb\
+i3cAAAj8AABDvGhlYWQXj7dYAAAA7AAAADZoaGVhCWn/FwAAASQAAAAkaG10eNC5+zgAAAHAAAAC\
+OGxvY2FuelzOAAAH3AAAAR5tYXhwANUBEgAAAUgAAAAgbmFtZe6psCEAAEy4AAADFXBvc3QELFTy\
+AABP0AAABY0AAQAAAAEAAOf5+OZfDzz1AAsEAAAAAADRlyIXAAAAAOJWUs3/OPzvBUsEiAAAAAgA\
+AgAAAAAAAAABAAAEiPzvAFwEJf84/XQFSwABAAAAAAAAAAAAAAAAAAAAjgABAAAAjgDhAAUAAAAA\
+AAIAAAABAAEAAABAAC4AAAAAAAEBewGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAACAAUDAAAA\
AAAAAAAAARAAAAAAAAAAAAAAAFBmRWQAQAAA7LcDOP84AFwEiAMRAAAAAQAAAAAAAAF2ACIAAAAA\
-AVUAAAGQAAACWAAAAFcAAAAjAAAAJQAAACT//wBkAAAEIwAABCUAAAHg/9wDugAAAwsAAALSAAAC\
-v/+6AdYAAAMLAAADDgAAAyf/yADIAAABaAAAAa4AAAEiAAABkAAAAXwAAAGQAAABkAAAAYEAAAGQ\
-AAABkAAAAYEAAAGZAAkBmAAJAfQAAAEEABQBBAAKAmsAJAISAAABwgAAAUIAAAFAAAABSv/+ASwA\
+AVUAAAGQAAACWAAAAFcAAAAjAAAAJQAAACT//wBkAAAAZAAABCMAAAQlAAAB4P/cA7oAAAMLAAAC\
+0gAAAr//ugHWAAADCwAAAw4AAAMn/8gAyAAAAWgAAAGuAAABIgAAAZAAAAF8AAABkAAAAZAAAAGB\
+AAABkAAAAZAAAAGBAAABmQAJAfQAAAEEABQBBAAKAmsAJAISAAABwgAAAUIAAAFAAAABSv/+ASwA\
AAIwAAABSgAAAUoAAABkAAABQAAAAUAAAAFAAAABQAAAAGQAAAE2AAAA5gAAATYAAAE7AAABOwAA\
ATsAAAE7AAABOwAAATsAAAE7AAABOwAAATsAAAE7AAABDQAAAMgAAAD/AAABCwAUAW4AAACMAAAA\
jAAAAQ0AMgFu//UAqQAAAToAAAFA//0AUAAAAVQAAABkAAABGAAAAlgAAAC2AAABkAAFAIIAAACC\
AAABLAAAASwAAADuAAAA/wAAAUkAAAGPAAAB2AAAAdgAAAIz//ADIP/hAXv/tAG4/9sBFv9+ARP/\
2wDcAAAA6P/kAr//tAIz/7QCv/+0Ayv/2wFf/9sCaf9+AV//fgJp/34BXwAAAf0ABQG1AAABtQAA\
AkQADQJEAA0BGAAAATYAAAEs//8BLAAAAPoAAADIAAABGP84APoAAADIAAAEDQAAAhwADAH0AAAB\
-9AAAAfQAAAH0AAAB9AAAAfQAAAB4AAAALQAAAhwAAAD6AAAA+v/oAcIAAAFIAAABQAAAAgoAAAIK\
-AAAAZAAAAAAAAwAAAAMAAAAcAAEAAAAAAuAAAwABAAAAHAAEAsQAAABgAEAABQAgAAAAIOAA4DDg\
-OeBI4FDgXOBi4GngjOCV4KTgqeCz4QHhu+Hn4gDiSeJk4mvig+Ss5MDk0eTq5QHlMeU55W3lguXQ\
-5eLmGOYk5jDmUOZV6RjpIOkl6V3qAuqk7Knst///AAAAAAAg4ADgMOA44EPgUOBc4GLgaeB64JTg\
-oOCp4LPhAeG54efh8uJA4mDiauKA5KDkwOTO5OHlAOUg5TnlZuWC5dDl4uYQ5iTmMOZQ5lXpEOkg\
-6SXpXeoC6qTsouy3//8AA//kIAUf1gAAAAAfvh+zH64fqAAAAAAAAB+CH3kfLAAAHkkAAAAAAAAA\
-AAAAAAAbkwAAAAAAAAAAGzcAABr0GqcalgAAGlkaThovGisAABdnF2MXLBaIFecAABPaAAEAAAAA\
-AAAAAABYAFoAAAAAAAAAAABcAIAAggAAAAAAAACEAAAAhgCiALQAvAC+AMQAAADaAOAA8gD0AAAB\
-FAAAAAAAAAEcAAAAAAAAAAABJAAAAAAAAAAAAAABKgAAAAAABwAIAAkAAAAKAAsADAANABIAEwAU\
-ABUAFgAAABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAJAAlACYAJwAoACkAKgAuAAAALwAxAAAA\
-MgAAAAAAMwAAADQAAAAAADUAAAA2ADcAOAA5ADoAOwA8AD0APgA/AEAAQQBCAEMARABFAEYARwBI\
-AEkASgBLAEwATQBOAAAATwAAAFAAAAAAAAAAUQAAAAAAAABSAFQAAAAAAFUAVgBXAFgAWQBaAFsA\
-XABdAF4AXwBgAGEAYgBjAGQAZQBmAGcAAAAAAAAAaABpAGoAawBsAAAAbQBuAG8AcQByAAAAcwAA\
-AAAAdAB1AHkAAAB6AAAAewAAAAAAAAB8AIEAggCDAAAAhACFAAAAAACGAIwAjQAAAI4AAACPAAAA\
-kAAAAQYAAAMAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAABAAAAAAAAAAAAAAAAAAAAAAA\
+9AAAAHgAAAAtAAACHAAAAPoAAAD6/+gBwgAAAUgAAAFAAAACCgAAAgoAAABkAAAAAAADAAAAAwAA\
+ABwAAQAAAAAC2AADAAEAAAAcAAQCvAAAAGAAQAAFACAAAAAg4ADgMOA54EjgUOBc4GLgaeCM4JXg\
+pOCp4LPhAeG74efiAOJJ4mTia+KD5KzkwOTR5OrlAeUx5TnlbeWC5dDl4uYY5iTmMOZQ5lXpFekg\
+6SXpXeoC6qTsqey3//8AAAAAACDgAOAw4DjgQ+BQ4FzgYuBp4HrglOCg4Kngs+EB4bnh5+Hy4kDi\
+YOJq4oDkoOTA5M7k4eUA5SDlOeVm5YLl0OXi5hDmJOYw5lDmVekR6SDpJeld6gLqpOyi7Lf//wAD\
+/+QgBR/WAAAAAB+/H7Qfrx+pAAAAAAAAH4IfeR8sAAAeSQAAAAAAAAAAAAAAABuTAAAAAAAAAAAb\
+NwAAGvQapxqWAAAaWRpOGi8aKwAAF2MXXxcoFoQV4wAAE9YAAQAAAAAAAAAAAFgAWgAAAAAAAAAA\
+AFwAgACCAAAAAAAAAIQAAACGAKIAtAC8AL4AxAAAANoA4ADyAPQAAAEUAAAAAAAAARwAAAAAAAAA\
+AAEkAAAAAAAAAAAAAAEiAAAAAAAHAAgACQAKAAsADAANAA4AEwAUABUAFgAXAAAAGAAZABoAGwAc\
+AB0AHgAfACAAIQAiAAAAIwAkACUAJgAnACgAKQAqAC4AAAAvADEAAAAyAAAAAAAzAAAANAAAAAAA\
+NQAAADYANwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4AAABP\
+AAAAUAAAAAAAAABRAAAAAAAAAFIAVAAAAAAAVQBWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMA\
+ZABlAGYAZwAAAAAAAABoAGkAagBrAGwAAABtAG4AbwBxAHIAAABzAAAAAAB0AHUAeQAAAHoAAAB7\
+AAAAAAAAAHwAgQAAAAAAAACCAIgAiQAAAIoAAACLAAAAjAAAAQYAAAMAAAAAAAAAAQIAAAACAAAA\
+AAAAAAAAAAAAAAAAAQAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAogAAAAqACoAKgA2AD4AbgB6\
-AIYAkgCwASQBgAHwAjQCwANSA7IDxgRkBPYFTgWOBhAGMgZGBpgG6AcIB04HjgfICBQIVAiaCPQJ\
-CAkuCVQJhgmiCcQJ7AoCChwKUgpgCmwKeAqKCqYKyArWCuoK/AsMC1gLaAuCC5wL1AwMDFoMpg0M\
-DW4N7A5kDooOpA7UDv4PVg9wD4oPsBAKECoQaBCQEKQQsBC+EM4Q9hEeEVYRYhFuEXoRhhGoEc4S\
-BBJSErITJhNME3oTzhRCFJYU1hUaFXAWihdiF/gYqhlsGf4a0BvkHNAdEh1WHaAduh3eHfIeBh42\
-HkYeWB50Hooerh7eH7ogdiCeILwg7iEsIV4hoCGyIcAh3CH2IhwiPiJsIoYisCLsIv4AAAACACIA\
-AAEyAqoAAwAHAC6xAQAvPLIHBADtMrEGBdw8sgMCAO0yALEDAC88sgUEAO0ysgcGAfw8sgECAO0y\
-MxEhESczESMiARDuzMwCqv1WIgJmAAABAAAAAAGRAZAAAwAAMREhEQGRAZD+cAABAAAAAAAAAAAA\
-AAAAMQAAAQAAAAAAVwQDACAAABE1NjU0JyY1NDcGFRQXFhUUBxYVFAcGFRQXJjU0NzY1NDUjElc/\
-FSVNTSUVP1cSIwIDAhhDNmA5NGY6MksiOWFNYRgYZkxgOSVKMjpmNDlgNkgAAQAAAAAAIwPoAAMA\
-ABEzESMjIwPo/BgAAQAAAfQAJQPoAAMAABEzESMlJQPo/gwAAf//A2QAJARgAAMAABEzByMkASQE\
-YPwAAgAAAUAAZAKeAAcADwAAEiImNDYyFhQCIiY0NjIWFEcqHR0qHR0qHR0qHQI6HSodHSr+6R0q\
-HR0qAAAABQAAAAAEJAGuAC8ANwA/AEcAUwAAITUzHgEzMjY1NCcuBDU0NjMyFhc3MxcjLgEjIgYV\
-FB4DFx4BFRQGIyInByAiJjQ2MhYUBCImNDYyFhQBETMyNjQmIwM1MxEjNTMyFhUUIwJOHhVPMik7\
-lBkaKhYRWT0kJxkeHgceD0owHzkQIhkyCE5NW09FLiMBmyodHSod/eoqHR0qHf6JKDxGRjzcRkbc\
-cYn6oDxLICEtKAcIFBQjFUNNCw4ZmzpIKBsPFw8JCwIVNzM6TiAgHSodHSodHSodHSoBaf6YYaZh\
-/noeAWgeZ2vSAAUAAAAABCQBrgAaACIAKgAyAD4AACEiJjU0NjMyFhc3MxcHJiMiBhQWMzI2NxcO\
-ATIiJjQ2MhYUBCImNDYyFhQBETMyNjQmIwM1MxEjNTMyFhUUIwMCZ3WCWiUpGx4eCCEkXjg2Njg2\
-TREjFFmxKh0dKh396iodHSod/okoPEZGPNxGRtxxifprZ2V3DBIepgSMbZhtST4KSlEdKh0dKh0d\
-Kh0dKgFp/phhpmH+eh4BaB5na9IAAAAD/9wAAgHeArMABwAPAE0AAAAiJjQ2MhYUBCImNDYyFhQX\
-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\
-JjUOAycyPgI1NCY1DgMnMj4CNTQmNQ4DJzI+AjU0JjUOAx4cVVc9BR4TEhISEhISEhwuODszJwYd\
-VVc9ARVWTUQJHVVXPQEVVk1ECR1VVz0BFVZNRAkdVVc9ARVWTUT9vANXKUh3RCEZOTYrKSssLScr\
-LC0nKywtJyssJUg9OjczORuqKUl4RAMNBCldQ1SGKUl4RAMNBCldQ1SGKUl4RAMNBCldQ1SGKUl4\
-RAMNBCldQ1QAAAACAAD/ZADhAbAACgAWAAA3IgYdATY3NjU0JjcyFhUUBwYjETMRNmcUKyImKx0H\
-IzlLUkQoI3omE7kPODsvGyYmMiNJTFICTP68NAAAAgAA/oYAxQF6AAMADAAAFzc1BxEVNxEjNQcR\
-NxyQkKkZrAFlLZYtAUnoNP3A4jMCQwEAAAIAAP6YAP8BaAADAB8AADcVNzUDIzUHNTc1BzU3NTMV\
-NzUzFTcVBxU3FQcVIzUHU1paHjU1NTUeWh01NTU1HVpGpxun/jejD1wPpw9aD6ifHKujD1wPpw9a\
-D6ifHAAAAAEAFP+EAQsAegAeAAAXNSYnBzAVIzUzNycwIzUzFRYXNzA1MxUjBgcXMDMVwygMM0g5\
-MzM5SCESNEg5IRM0OXw7Jg0zO0oyMkg5IhEzOUciETRIAAQAAP9qAWwBsAAOABwAKwA6AAA3DgEd\
-ATI3Njc2NTQnJiM3MhYVFAcGBwYjETMRNhcOAR0BMjc2NzY1NCcmIzcyFhUUBwYHDgEjETMRNk4R\
-Hg4eHwwEChARGR0rCRgrNS8fGdERHRAeHQsGCxAPFh8qCxkoFjcWHht9AR4QxikrNA0ZHhQVJjkh\
-EiA5NEACRv7BMiYBHRHGKS8wExMcFhUmNiQWHD4vGyUCRv7BMgAAAQAA/wYAjAD6AA4AADcVBhUU\
-FxUuAzQ+AoxQUBolMhsbMiX6FEOjpkAUDx86WnBaOh8AAAEAAP8GAIwA+gAOAAA1HgMUDgIHNTY1\
-NCcaJTIbGzIlGlBQ+g8fOlpwWjofDxRApqNDAAACADL/ZAENAbAACgAWAAA3IgYVFBcWFzU0Jicy\
-FxEzESInJjU0NqsXIC4uGiozOiMjOlJPOXomGzBFQgPLEh4mNAFE/bRSUEUjMgAABP/1/2oBbAGw\
-AA4AHQAsADsAADcjIgcGFRQXFhcWMzU0JicyFxEzESImJyYnJjU0NhcOAR0BMjc2NzY1NCcmIzcy\
-FhUUBwYHDgEjETMRNlcBERAKBAwfHg4eKzAZHxY3FysYCSvpER0QHh0LBgsQDxYfKgsZKBY3Fh4b\
-fRUUHhkNNCspxhAeJzIBP/26JRs0OSASITkmAR0RxikvMBMTHBYVJjYkFhw+LxslAkb+wTIAAAAA\
-AQAA/sAAqQFAABMAABMzFTcVBxU3FQcVIzUHNTc1BzU3RB5HR0dHHkREREQBQKIOXA5/DloPqKIO\
-XA5/DloPAAMAAP6YAToBaAAjACcAKwAANzUzFTcVBxU3FQcVIzUHFSM1DwEjNQc1NzUHNTc1MxU3\
-NTMVAzUHFTcVNzXpHjMzMzMePR48AR4zMzMzHj0eHj1bPcCong9cD58PWg+1qhKspBCong9cD58P\
-Wg+1qhKspP79nhGfuJ4RnwAB//0AAAE/APQAGAAANwYjIiY1ND8BNi8BJjU0NjMyMRcFFhUUBxIC\
-AwcJBs8ODs0ICwcBAgEfDg4BARAICgNJBwZPAwsKEgFrBg4NBQAAAAEAAAAAAFAAUAAJAAA1NDYy\
-FhQGIyImFyIXFxEQGCgRFxciFxgAAAABAAAAAAFUADIAAwAAMTUhFQFUMjIAAAABAAAAAABkARgA\
-AwAAMwMzAygoZCgBGP7oAAAAAQAAAAABGAE1AAUAADEbASMnB4yMQVhaATX+y8bGAAACAAAAAAJY\
-AUoADgAZAAAxNDYzMh4CFSMuASIGByEiJjQ2MzIWFRQGs3k5a1UzDwui4KILARwXJSUXGSMjmLIs\
-UYBNboaGbiQwJCQYGSMAAAEAAAAAALYBLQAYAAATMhYXFhUUBw4BIzAnJjU0NjU0Iy4BNTQ2Vhsb\
-EBoyGUQQBgFHFBsoLQEtDBEdMD08HS0DAQIIaxMPASYcHjEAAAAAAgAFAAABjgH1ABEAIwAANxM2\
-MzIXFhUUBwMGIyInJjU0JxM2MzIXFhUUBwMGIyInJjU0mM0LCgYDCwXMCQsEBguLzQoLBgMLBcwI\
-DAQGCyMBwBICBQoGDP4/EQMGDAcHAcASAgUKBgz+PxEDBgwHAAEAAP8GAIIA+gADAAA1MxEjgoL6\
-/gwAAAEAAAAAAIIA+gADAAA1MxUjgoL6+gAAAAEAAP+DASwAAAADAAAxIRUhASz+1H0AAAEAAAAA\
-ASwAfQADAAA1IRUhASz+1H19AAEAAP5+AOsBhwATAAATFwcXJiMiBhUUFyY1NDYzMhcnNym9Z2wy\
-NB8mOHg0JSIih2QBh+XZzy4kHTU0S00jLRW8tAAAAQAA/w0BAADAABYAADcOAiMiJjU0NjIWFRQH\
-MjY3NjIXAyerAxkaEys3JjgpFyIzIQIVA5YwPAEHBCkoHyAeGR0bISwCAv5vEAAAAAEAAP4MAUgA\
-wAAkAAAXBiMiJjU0NjMyFhUUBzI/AQYjIiY1NDYzMhYVFAcyNzYyFwMnqyghKzcnGxwpF0ELPDYY\
-KzcnGxwpF0guAhUDxS3EDCgoICAfGR0bIsoMKSgfIB4ZHRtNAgL9bgwAAAEAAP4MAY8BwAA2AAA3\
-BiMiJjU0NjMyFhUUBzI/ASIOASMiJjU0NjMyFhUUBzI3NjIXAScTBiMiJjU0NjMyFhUUBzI39igf\
-KzcnGxwpFz8LOgEgHBMrNycbHCkXSC4BFgP+9C1VKCErNycbHCkXQQs8DCgoICAfGR0bIssJBCko\
-HyAeGR0bTQIC/G4MASQMKCggIB8ZHRsiAAAAAAEAAP0MAdoBwABFAAATBiMiJjU0NjMyFhUUBzI/\
-AQYjIiY1NDYzMhYVFAcyPwEGIyImNTQ2MzIWFRQHMj8BIg4BIyImNTQ2MhYVFAcyNzYyFwEnqygh\
-KzcnGxwpF0ELOighKzcnGxwpF0ELOigfKzcnGxwpFz8LOgEgHBMrNyY4KRdILgIVA/6pLf48DCgo\
-ICAfGR0bIsoMKCggIB8ZHRsiygwoKCAgHxkdGyLLCQQpKB8gHhkdG00CAvtuDAAAAAEAAP0MAhkC\
-rgBWAAAlBiMiJjU0NjMyFhUUBzI/AQYjIiY1NDYzMhYVFAcyPwEiDgEjIiY1NDYzMhYVFAcyNzYy\
-FwEnEwYjIiY1NDYzMhYVFAcyPwEGIyImNTQ2MzIWFRQHMjcBPyghKzcnGxwpF0ELNigfKzcnGxwp\
-Fz8LNAEgHBMrNycbHCkXSC4BFgP+ai1VKCErNycbHCkXQQs6KCErNycbHCkXQQs0DCgoICAfGR0b\
-IsQMKCggIB8ZHRsixwkEKSgfIB4ZHRtNAgL6gAwBJAwoKCAgHxkdGyLIDCgoICAfGR0bIgAD//D/\
-BgImAPoABwAPABMAADYiJjQ2MhYUACImNDYyFhQFATMBUDIjIzIjAYgyIyMyI/3SAbh+/kdLIzIj\
-IzL+zyMyIyMyWgH0/gwABP/h/wYDBwD6AAcADwATABcAADYiJjQ2MhYUACImNDYyFhQFATMBMwEz\
-AUEyIyMyIwJ3MiMjMiP84wG4e/5HeQG4e/5HSyMyIyMy/s8jMiMjMloB9P4MAfT+DAAC/7T/iAF8\
-ARgAEQA7AAA3FjMyNjc2NTQnJiMiBgcGFRQXIicHMzIUKwEiNDsBEzY1NCMiDgMHBiY3Njc2MzIW\
-Fz4BMzIWFRQGxQIFEjIODQ8CBBI1Cw8nKhktNAsL4QsLS2gGCwgMDwsZCgUbBTEPFyUjJAcdJiMe\
-LVkoATUkICYlBQEzHCckKS4geh4eAR0SDA8HFBIsEAgPCVgQGRMaHg80MElrAAAB/9v/9gG+ARgA\
-UwAANwYHBisBIj8BNiYjIgYHBiY3PgMzMhc2MzIXPgEzMhYVFA8BBhUUMzI3PgU3NhYHDgIjIiY1\
-ND8BNjU0IyIPAQYHJwYmPwE2NTQjIgdQBwgEBDUNDUIEBggNFiQFFQQUECIfEjcLJCQtCQspExkk\
-BS8ECAEEBQsHDAMNAQYVBhIXLR8VGQU0ARUbCEEIDyYNCARDARUbCBURAwEapw0PGjkIDAklGjAS\
-KCgoEBgjGgsPfQsJDgIDCQYPBRIBCQ0LHx8aFhMNDYgCBA4UqxYBAQEPCKsCBA4UAAAAAf9+/2AB\
-XgG4AEEAAAciJjU0NjMyFhUUBwYVFDMyPgc3IyI1NDsBPgEzMhYVFAYjIiY1NDc2NCMiDgcHMzIU\
-KwEOATIgMBcTEhcSChkLEA8LDQoODRQKNRMRQRRpNCAwFxMSFxIKGQcMCgcIBQYDBgE2ExM/IXag\
-JiAaIhQPDgsHDQ4GERMlJDw3VScVE0tfJiAaIhQPDgsGHAULChQNGg0eBijFwQAAAf/bAAABEwET\
-ACoAADc+ASYjIgYPAQYHBisBIj8BNiYjIgYHBiY3PgEzMhYXNjMyFhUUIyImNTTQBQMDBRQkCT8H\
-CAQENQ0NQgQGCA4XIgUVBCE4IhsdBB8kGiArDxvnAwcELBicEQMBGqMNDxo1CAwJPEAaDSceGTcW\
-DRQAAAABAAAAAADcARgAMQAAMyImNTQ2MhYVFAcWMzI2NTQuAicmNTQ2MzIWFRQGIyImNTQ3JiMi\
-BhUUHgIXFhUUUB4yFRoXEAwTFiEJCxgGOjctIjYWEA0WBw8RDxkREh4FMCsbEBYQDBIQEBYSCw8H\
-DQQlMCMoJBgQGBQOAw4TEQ0JEwwRAyArVQAAAf/k//wA5wEPADwAACcGLgE/ASIGIyImDgEHBicm\
-Nz4BNx4BMzI2MzIXFhQPAQYVFDMyNjMyFxY3NiciNTQ2MzIVFAYrAS4BIyIKBgsBBbAFHwwDFAwf\
-BQ8EAwgMCwEOLxUiKwQIBQsHngICAQ0GGiMNCAUHJBINJigeHRglCA4DBAgNBsEGBgIyBQ0LCRIe\
-MAIBBgsBAREHpwYCAwMaCg4NAx0OFTceLwMYAAX/tP+IBUsBGAAQACIAtQDGANcAACUWMzI2NzY0\
-JyYjIgYHBhUUBRYzMjY3NjU0JyYjIgYHBhUUFyInBzMyFCsBIjQ7ARM2NTQjIg4DBwYmNzY3NjMy\
-Fhc+ATMyFz4BMzIWFz4BMzIXPgEzMhYXPgEzMhc+ATMyFhc+ATMyFhUUBiMiJwczMhQrASI0OwET\
-NjU0IyIGBxUUBiMiJwczMhQrASI0OwETNjU0IyIGBxUUBiMiJwczMhQrASI0OwETNjU0IyIGBxUU\
-BiUWMzI2NzY0JyYjIgYHBhUUBRYzMjY3NjQnJiMiBgcGFRQCCgIFEjIODQ8CBBI1Cw/+ywIFEjIO\
-DQ8CBBI1Cw8TFhktNAsL4QsLS2gGCwgMDwsZCgUbBTEPFyUjJAcdJiMvExIiGyMkBx0mIy8TEiIb\
-IyQHHSYjLxMSIhsjJAcdJiMeLWtJFhktNAsL4QsLS2gGCw4UFGtJFhktNAsL4QsLS2gGCw4UFGtJ\
-FhktNAsL4QsLS2gGCw4UFGsDgwIFEjIODQ8CBBI1Cw/+ywIFEjIODQ8CBBI1Cw8oATUkIUoFATMc\
-JyQpBgE1JCAmJQUBMxwnJCkuIHoeHgEdEgwPBxQSLBAIDwlYEBkTGh4PMhwWExoeDzIcFhMaHg8y\
-HBYTGh4PNDBHbSB6Hh4BHRIMDxgjAUdtIHoeHgEdEgwPGCMBR20geh4eAR0SDA8YIwFHbSgBNSQh\
-SgUBMxwnJCkGATUkIUoFATMcJyQpAAT/tP+IBAYBGABvAIAAkQCjAAAhIicHMzIUKwEiNDsBEzY1\
-NCMiBgcVFAYjIicHMzIUKwEiNDsBEzY1NCMiBgcVFAYjIicHMzIUKwEiNDsBEzY1NCMiDgMHBiY3\
-Njc2MzIWFz4BMzIXPgEzMhYXPgEzMhc+ATMyFhc+ATMyFhUUBicWMzI2NzY0JyYjIgYHBhUUBRYz\
-MjY3NjQnJiMiBgcGFRQFFjMyNjc2NTQnJiMiBgcGFRQDUhYZLTQLC+ELC0toBgsOFBRrSRYZLTQL\
-C+ELC0toBgsOFBRrSRYZLTQLC+ELC0toBgsIDA8LGQoFGwUxDxclIyQHHSYjLxMSIhsjJAcdJiMv\
-ExIiGyMkBx0mIx4ta0wCBRIyDg0PAgQSNQsP/ssCBRIyDg0PAgQSNQsP/ssCBRIyDg0PAgQSNQsP\
-IHoeHgEdEgwPGCMBR20geh4eAR0SDA8YIwFHbSB6Hh4BHRIMDwcUEiwQCA8JWBAZExoeDzIcFhMa\
-Hg8yHBYTGh4PNDBHbSgBNSQhSgUBMxwnJCkGATUkIUoFATMcJyQpBgE1JCAmJQUBMxwnJCkAAAP/\
-tP+IAsEBGABMAF4AbwAAMyInBzMyFCsBIjQ7ARM2NTQjIg4DBwYmNzY3NjMyFhc+ATMyFz4BMzIW\
-Fz4BMzIWFRQGIyInBzMyFCsBIjQ7ARM2NTQjIgYHFRQGJxYzMjY3NjU0JyYjIgYHBhUUBRYzMjY3\
-NjQnJiMiBgcGFRTIFhktNAsL4QsLS2gGCwgMDwsZCgUbBTEPFyUjJAcdJiMvExIiGyMkBx0mIx4t\
-a0kWGS00CwvhCwtLaAYLDhQUa0wCBRIyDg0PAgQSNQsPAVUCBRIyDg0PAgQSNQsPIHoeHgEdEgwP\
-BxQSLBAIDwlYEBkTGh4PMhwWExoeDzQwR20geh4eAR0SDA8YIwFHbSgBNSQgJiUFATMcJyQpBgE1\
-JCFKBQEzHCckKQAAAAL/2/+IAysBGAByAIMAACU2NzYzMhYXPgEzMhYVFAYjIicHMzIUKwEiNDsB\
-EzY1NCMiDgMHDgEjIiY1ND8BNjU0IyIPAQYHJwYmPwE2NTQjIg8BBgcGKwEiPwE2JiMiBgcGJjc+\
-AzMyFzYzMhc+ATMyFhUUDwEGFRQzMjc+ARcWMzI2NzY0JyYjIgYHBhUUAa0xFBgiIyQHHSYjHi1r\
-SRYZLTQLC+ELC0toBgsHDxIOGAgaPC0VGQU0ARUbCEEIDyYNCARDARUbCEQHCAQENQ0NQgQGCA0W\
-JAUVBBQQIh8SNwskJC0JCykTGSQFLwQIAQQPJdECBRIyDg0PAgQSNQsPd2wYHRMaHg80MEdtIHoe\
-HgEdEgwPDR8bNRA2OBYTDQ2IAgQOFKsWAQEBDwirAgQOFKwRAwEapw0PGjkIDAklGjASKCgoEBgj\
-GgsPfQsJDgIINTkBNSQhSgUBMxwnJCkAAAAAAv/b/2ADGQG4AFMAlQAANwYHBisBIj8BNiYjIgYH\
-BiY3PgMzMhc2MzIXPgEzMhYVFA8BBhUUMzI3PgU3NhYHDgIjIiY1ND8BNjU0IyIPAQYHJwYmPwE2\
-NTQjIgcTIiY1NDYzMhYVFAcGFRQzMj4HNyMiNTQ7AT4BMzIWFRQGIyImNTQ3NjQjIg4HBzMyFCsB\
-DgFQBwgEBDUNDUIEBggNFiQFFQQUECIfEjcLJCQtCQspExkkBS8ECAEEBQsHDAMNAQYVBhIXLR8V\
-GQU0ARUbCEEIDyYNCARDARUbCPUgMBcTEhcSChkLEA8LDQoODRQKNRMRQRRpNCAwFxMSFxIKGQcM\
-CgcIBQYDBgE2FBQ/IXYVEQMBGqcNDxo5CAwJJRowEigoKBAYIxoLD30LCQ4CAwkGDwUSAQkNCx8f\
-GhYTDQ2IAgQOFKsWAQEBDwirAgQOFP6fJiAaIhQPDgsHDQ4GERMlJDw3VScVE0tfJiAaIhQPDgsG\
-HAULChQNGg0eBijFwQAAAAH/fv9gAmkBuAB0AAAlIw4BIyImNTQ2MzIWFRQHBhUUMzI+BzcjIjU0\
-OwE+ATMyFhUUBiMiJjU0NzY0IyIHBgcXPgEzMhYVFAYjIiY1NDc2NCMiDgcHMzIUKwEOASMiJjU0\
-NjMyFhUUBwYVFDMyPgcBX5shdl8gMBcTEhcSChkLEA8LDQoODRQKNRMRQRRpNCAwFxMSFxIKGSYX\
-AwGbFGk0IDAXExIXEgoZBwwKBwgFBgMGATYUFD8hdl8gMBcTEhcSChkLEA8LDQoPDRTmxcEmIBoi\
-FA8OCwcNDgYREyUkPDdVJxUTS18mIBoiFA8OCwYccwwGAUtfJiAaIhQPDgsGHAULChQNGg0eBijF\
-wSYgGiIUDw4LBw0OBhETJSQ8N1UAAAAB/37/YAN0AbgAqgAAEzM+ATMyFhUUBiMiJjU0NzY0IyIH\
-BgcXPgEzMhYVFAYjIiY1NDc2NCMiDgcHMzIUKwEOASMiJjU0NjMyFhUUBwYVFDMyPgc3Iw4BIyIm\
-NTQ2MzIWFRQHBhUUMzI+BzcjDgEjIiY1NDYzMhYVFAcGFRQzMj4HNyMiNTQ7AT4BMzIWFRQGIyIm\
-NTQ3NjQjIg4CBwbMnBRpNCAwFxMSFxIKGSYXAwGbFGk0IDAXExIXEgoZBwwKBwgFBgMGATYTEz8h\
-dl8gMBcTEhcSChkLEA8LDQoPDRQKmyF2XyAwFxMSFxIKGQsQDwsNCg4NFQqbIXZfIDAXExIXEgoZ\
-CxAPCw0KDg0UCjUTEUEUaTQgMBcTEhcSChkOFQ4IBgIBDktfJiAaIhQPDgsGHHMMBgFLXyYgGiIU\
-Dw4LBhwFCwoUDRoNHgYoxcEmIBoiFA8OCwcNDgYREyUkPDdVJ8XBJiAaIhQPDgsHDQ4GERMlJDs4\
-VSfFwSYgGiIUDw4LBw0OBhETJSQ8N1UnFRNLXyYgGiIUDw4LBhwTJyIbCgAB/37/YASAAbgA4AAA\
-ARc+ATMyFhUUBiMiJjU0NzY0IyIHBgcXPgEzMhYVFAYjIiY1NDc2NCMiDgcHMzIUKwEOASMiJjU0\
-NjMyFhUUBwYVFDMyPgc3Iw4BIyImNTQ2MzIWFRQHBhUUMzI+BzcjDgEjIiY1NDYzMhYVFAcGFRQz\
-Mj4HNyMOASMiJjU0NjMyFhUUBwYVFDMyPgc3IyI1NDsBPgEzMhYVFAYjIiY1NDc2NCMiBwYHFz4B\
-MzIWFRQGIyImNTQ3NjQjIg4HAdicFGk0IDAXExIXEgoZJhcDAZsUaTQgMBcTEhcSChkHDAoHCAUG\
-AwYBNhMTPyF2XyAwFxMSFxIKGQsQDwsNCg8NFAqbIXZfIDAXExIXEgoZCxAPCw0KDg0UCpshdl8g\
-MBcTEhcSChkLEA8LDQoPDRQKmyF2XyAwFxMSFxIKGQsQDwsNCg4NFAo1ExFBFGk0IDAXExIXEgoZ\
-JhcDAZsUaTQgMBcTEhcSChkHDAoHCAUGAwYBDwFLXyYgGiIUDw4LBhxzDAYBS18mIBoiFA8OCwYc\
-BQsKFA0aDR4GKMXBJiAaIhQPDgsHDQ4GERMlJDw3VSfFwSYgGiIUDw4LBw0OBhETJSM8N1YnxcEm\
-IBoiFA8OCwcNDgYREyUkPDdVJ8XBJiAaIhQPDgsHDQ4GERMlJDw3VScVE0tfJiAaIhQPDgsGHHMM\
-BgFLXyYgGiIUDw4LBhwFCgsTDhkOHQAAAAADAAD/YALfAbgANgBxALYAADMiJjU0NjMyFhUUBxYz\
-MjY1NC4DJy4CNTQ2MzIWFRQGIyImNTQ3JiMiBhUUHgIXHgEVFCUGLgE/ASYjIgYjIg4BBwYnJjc+\
-ATcWMzI2MzIXFhQPAQYVFB4BFxY+AScuATU0NjMyFRQGKwEuASMiBSImNTQ2MzIWFRQHDgEeARUU\
-FjI+BzcjIjU0OwE+ATMyFhUUBiMiJjU0NzY0IyIOBwczMhQrAQ4BUB4yEgwOFwwGGBYhAwwFFwMU\
-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\
-MgAwADEAOQAgAEoAZQBhAG4ALQBGAHIAYQBuAGMAbwBpAHMAIABNAG8AaQBuAGUALgAgAFQAaABp\
-AHMAIABmAG8AbgB0ACAAaQBzACAAbABpAGMAZQBuAHMAZQBkACAAdQBuAGQAZQByACAAdABoAGUA\
-IABTAEkATAAgAE8AcABlAG4AIABGAG8AbgB0ACAATABpAGMAZQBuAHMAZQAgAFwAKABoAHQAdABw\
-ADoALwAvAHMAYwByAGkAcAB0AHMALgBzAGkAbAAuAG8AcgBnAC8ATwBGAEwAXAApAC4AAENvcHly\
-aWdodCBcMjUxIDIwMTgtMjAxOSBKZWFuLUZyYW5jb2lzIE1vaW5lLiBUaGlzIGZvbnQgaXMgbGlj\
-ZW5zZWQgdW5kZXIgdGhlIFNJTCBPcGVuIEZvbnQgTGljZW5zZSBcKGh0dHA6Ly9zY3JpcHRzLnNp\
-bC5vcmcvT0ZMXCkuAABhAGIAYwAyAHMAdgBnAABhYmMyc3ZnAABSAGUAZwB1AGwAYQByAABSZWd1\
-bGFyAABGAG8AbgB0AEYAbwByAGcAZQAgADoAIABhAGIAYwAyAHMAdgBnACAAOgAgADEANAAtADQA\
-LQAyADAAMgAzAABGb250Rm9yZ2UgOiBhYmMyc3ZnIDogMTQtNC0yMDIzAABhAGIAYwAyAHMAdgBn\
-AABhYmMyc3ZnAABWAGUAcgBzAGkAbwBuACAAAFZlcnNpb24gAABhAGIAYwAyAHMAdgBnAABhYmMy\
-c3ZnAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJIAAAABAAIBAgADAQMBBAEF\
-AQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEB\
-IgEjASQBJQEmAScBKAEpASoBKwEsAS0BLgEvATABMQEyATMBNAE1ATYBNwE4ATkBOgE7ATwBPQE+\
-AT8BQAFBAUIBQwFEAUUBRgFHAUgBSQFKAUsBTAFNAU4BTwFQAVEBUgFTAVQBVQFWAVcBWAFZAVoB\
-WwFcAV0BXgFfAWABYQFiAWMBZAFlAWYBZwFoAWkBagFrAWwBbQFuAW8BcAFxAXIBcwF0AXUBdgF3\
-AXgBeQF6AXsBfAF9AX4BfwGAAYEBggGDAYQBhQGGAYcBiAGJAYoBiwGMAY0BjgGPBi5ub2RlZgd1\
-bmlFMDAwB3VuaUUwMzAHdW5pRTAzOAd1bmlFMDM5B3VuaUUwNDMHdW5pRTA0NQd1bmlFMDQ2B3Vu\
-aUUwNDcHdW5pRTA0OAd1bmlFMDUwB3VuaUUwNUMHdW5pRTA2Mgd1bmlFMDY5B3VuaUUwN0EHdW5p\
-RTA3Qgd1bmlFMDdDB3VuaUUwN0QHdW5pRTA3RQd1bmlFMDgwB3VuaUUwODEHdW5pRTA4Mgd1bmlF\
-MDgzB3VuaUUwODQHdW5pRTA4NQd1bmlFMDg2B3VuaUUwODcHdW5pRTA4OAd1bmlFMDg5B3VuaUUw\
-OEEHdW5pRTA4Qgd1bmlFMDhDB3VuaUUwOTQHdW5pRTA5NQd1bmlFMEEwB3VuaUUwQTEHdW5pRTBB\
-Mgd1bmlFMEEzB3VuaUUwQTQHdW5pRTBBOQd1bmlFMEIzB3VuaUUxMDEHdW5pRTFCOQd1bmlFMUJC\
-B3VuaUUxRTcHdW5pRTFGMgd1bmlFMUY0B3VuaUUxRjcHdW5pRTFGOQd1bmlFMUZDB3VuaUUxRkUH\
-dW5pRTFGRgd1bmlFMjAwB3VuaUUyNDAHdW5pRTI0MQd1bmlFMjQyB3VuaUUyNDMHdW5pRTI0NAd1\
-bmlFMjQ1B3VuaUUyNDYHdW5pRTI0Nwd1bmlFMjQ4B3VuaUUyNDkHdW5pRTI2MAd1bmlFMjYxB3Vu\
-aUUyNjIHdW5pRTI2Mwd1bmlFMjY0B3VuaUUyNmEHdW5pRTI2Ygd1bmlFMjgwB3VuaUUyODEHdW5p\
-RTI4Mgd1bmlFMjgzB3VuaUU0QTAHdW5pRTRBMgd1bmlFNEE0B3VuaUU0QTgHdW5pRTRBQwd1bmlF\
-NEMwB3VuaUU0Q0UHdW5pRTREMQd1bmlFNEUxB3VuaUU0RTIHdW5pRTRFMwd1bmlFNEU0B3VuaUU0\
-RTUHdW5pRTRFNgd1bmlFNEU3B3VuaUU0RTgHdW5pRTRFOQd1bmlFNEVBB3VuaUU1MDAHdW5pRTUw\
-MQd1bmlFNTIwB3VuaUU1MjEHdW5pRTUyMgd1bmlFNTIzB3VuaUU1MjQHdW5pRTUyNQd1bmlFNTI5\
-B3VuaUU1MkEHdW5pRTUyQgd1bmlFNTJDB3VuaUU1MkQHdW5pRTUyRgd1bmlFNTMwB3VuaUU1MzEH\
-dW5pRTUzOQd1bmlFNTY2B3VuaUU1NjcHdW5pRTU2OQd1bmlFNTZDB3VuaUU1NkQHdW5pRTU4Mgd1\
-bmlFNUQwB3VuaUU1RTIHdW5pRTYxMAd1bmlFNjEyB3VuaUU2MTQHdW5pRTYxOAd1bmlFNjI0B3Vu\
-aUU2MzAHdW5pRTY1MAd1bmlFNjU1B3VuaUU5MTAHdW5pRTkxMQd1bmlFOTEyB3VuaUU5MTQHdW5p\
-RTkxNQd1bmlFOTE4B3VuaUU5MjAHdW5pRTkyNQd1bmlFOTVEB3VuaUVBMDIHdW5pRUFBNAd1bmlF\
-Q0EyB3VuaUVDQTMHdW5pRUNBNQd1bmlFQ0E3B3VuaUVDQTkHdW5pRUNCNwAAAAAAAAH//wACAAEA\
-AAAAAAAADAAUAAQAAAACAAAAAQAAAAEAAAAAAAEAAAAA399K6gAAAADRlyIXAAAAAOBezeg=\
+AAAAAAAAAAAAAAAAAAAiAogAAAAqACoAKgA2AD4AbgB6AIYAkgCwAMIBNgGSAgICRgLSA2QDxAPY\
+BHYFCAVgBaAGIgZEBlgGqgb6BxoHYAegB9oIJghmCKwIwAjmCQwJPglaCXwJpAm6CdQKCgoYCiQK\
+MApCCl4KgAqOCqIKtArECxALIAs6C1QLjAvEDBIMXgzEDSYNpA4cDkIOXA6MDrYPDg8oD0IPaA/C\
+D+IQIBBIEFwQaBB2EIYQrhDWEQ4RGhEmETIRPhFgEYYRvBIKEmoS3hMEEzIThhP6FE4UjhTSFSgW\
+QhcaF7AYYhkkGbYaiBucHIgcyh0OHVgdch2WHaodwB3wHgAeEh4uHkQeaB6YH3QgMCBOIIAgkiCg\
+ILwg1iD8IR4hTCFmIZAhzCHeAAAAAgAiAAABMgKqAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDt\
+MgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIgEQ7szMAqr9ViICZgAAAQAAAAABkQGQ\
+AAMAADERIREBkQGQ/nAAAQAAAAAAAAAAAAAAADEAAAEAAAAAAFcEAwAgAAARNTY1NCcmNTQ3BhUU\
+FxYVFAcWFRQHBhUUFyY1NDc2NTQ1IxJXPxUlTU0lFT9XEiMCAwIYQzZgOTRmOjJLIjlhTWEYGGZM\
+YDklSjI6ZjQ5YDZIAAEAAAAAACMD6AADAAARMxEjIyMD6PwYAAEAAAH0ACUD6AADAAARMxEjJSUD\
+6P4MAAH//wNkACQEYAADAAARMwcjJAEkBGD8AAIAAAFAAGQCngAHAA8AABIiJjQ2MhYUAiImNDYy\
+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\
+hilJeEQDDQQpXUNUhilJeEQDDQQpXUNUAAAABQAA/VUBPAISACQAMgBAAE4AXAAAGQEzHgYVFAcW\
+FRQHFhUUBxYVFAcWFRQHNjU0LgMjNR4DFzQ2NTQuAyceAxc0NjU0LgMnHgMXNDY1NC4DJx4DFzQ2\
+NTQuAx4GJzM7OC4cEhISEhISEhMeBSk+SEAWCURNVhUBKT5IQRYJRE1WFQEpPkhBFglETVYVASk+\
+SEEWCURNVhUBKT5IQf67A1cbOTM3Oj1IJSwrJy0sKyctLCsnLSwrKSs2ORkhN2RFNBirJFRDXSkD\
+DQQ3ZUUzGaskVENdKQQMBDdlRTMZqyRUQ10pAw4DN2VFMxmrJFRDXSkEDAQ3ZUUzGQAABQAA/bwB\
+PAJ5ACMAMAA9AEoAVwAAGQEzMj4CNTQnFhUUBxYVFAcWFRQHFhUUBxYVFA4FBzUyPgI1NCY1DgMn\
+Mj4CNTQmNQ4DJzI+AjU0JjUOAycyPgI1NCY1DgMeHFVXPQUeExISEhISEhIcLjg7MycGHVVXPQEV\
+Vk1ECR1VVz0BFVZNRAkdVVc9ARVWTUQJHVVXPQEVVk1E/bwDVylId0QhGTk2KykrLC0nKywtJyss\
+LScrLCVIPTo3MzkbqilJeEQDDQQpXUNUhilJeEQDDQQpXUNUhilJeEQDDQQpXUNUhilJeEQDDQQp\
+XUNUAAAAAgAA/2QA4QGwAAoAFgAANyIGHQE2NzY1NCY3MhYVFAcGIxEzETZnFCsiJisdByM5S1JE\
+KCN6JhO5Dzg7LxsmJjIjSUxSAkz+vDQAAAIAAP6GAMUBegADAAwAABc3NQcRFTcRIzUHETcckJCp\
+GawBZS2WLQFJ6DT9wOIzAkMBAAACAAD+mAD/AWgAAwAfAAA3FTc1AyM1BzU3NQc1NzUzFTc1MxU3\
+FQcVNxUHFSM1B1NaWh41NTU1HlodNTU1NR1aRqcbp/43ow9cD6cPWg+onxyrow9cD6cPWg+onxwA\
+AAABABT/hAELAHoAHgAAFzUmJwcwFSM1MzcnMCM1MxUWFzcwNTMVIwYHFzAzFcMoDDNIOTMzOUgh\
+EjRIOSETNDl8OyYNMztKMjJIOSIRMzlHIhE0SAAEAAD/agFsAbAADgAcACsAOgAANw4BHQEyNzY3\
+NjU0JyYjNzIWFRQHBgcGIxEzETYXDgEdATI3Njc2NTQnJiM3MhYVFAcGBw4BIxEzETZOER4OHh8M\
+BAoQERkdKwkYKzUvHxnRER0QHh0LBgsQDxYfKgsZKBY3Fh4bfQEeEMYpKzQNGR4UFSY5IRIgOTRA\
+Akb+wTImAR0RxikvMBMTHBYVJjYkFhw+LxslAkb+wTIAAAEAAP8GAIwA+gAOAAA3FQYVFBcVLgM0\
+PgKMUFAaJTIbGzIl+hRDo6ZAFA8fOlpwWjofAAABAAD/BgCMAPoADgAANR4DFA4CBzU2NTQnGiUy\
+GxsyJRpQUPoPHzpacFo6Hw8UQKajQwAAAgAy/2QBDQGwAAoAFgAANyIGFRQXFhc1NCYnMhcRMxEi\
+JyY1NDarFyAuLhoqMzojIzpSTzl6JhswRUIDyxIeJjQBRP20UlBFIzIAAAT/9f9qAWwBsAAOAB0A\
+LAA7AAA3IyIHBhUUFxYXFjM1NCYnMhcRMxEiJicmJyY1NDYXDgEdATI3Njc2NTQnJiM3MhYVFAcG\
+Bw4BIxEzETZXAREQCgQMHx4OHiswGR8WNxcrGAkr6REdEB4dCwYLEA8WHyoLGSgWNxYeG30VFB4Z\
+DTQrKcYQHicyAT/9uiUbNDkgEiE5JgEdEcYpLzATExwWFSY2JBYcPi8bJQJG/sEyAAAAAAEAAP7A\
+AKkBQAATAAATMxU3FQcVNxUHFSM1BzU3NQc1N0QeR0dHRx5EREREAUCiDlwOfw5aD6iiDlwOfw5a\
+DwADAAD+mAE6AWgAIwAnACsAADc1MxU3FQcVNxUHFSM1BxUjNQ8BIzUHNTc1BzU3NTMVNzUzFQM1\
+BxU3FTc16R4zMzMzHj0ePAEeMzMzMx49Hh49Wz3AqJ4PXA+fD1oPtaoSrKQQqJ4PXA+fD1oPtaoS\
+rKT+/Z4Rn7ieEZ8AAf/9AAABPwD0ABgAADcGIyImNTQ/ATYvASY1NDYzMjEXBRYVFAcSAgMHCQbP\
+Dg7NCAsHAQIBHw4OAQEQCAoDSQcGTwMLChIBawYODQUAAAABAAAAAABQAFAACQAANTQ2MhYUBiMi\
+JhciFxcREBgoERcXIhcYAAAAAQAAAAABVAAyAAMAADE1IRUBVDIyAAAAAQAAAAAAZAEYAAMAADMD\
+MwMoKGQoARj+6AAAAAEAAAAAARgBNQAFAAAxGwEjJweMjEFYWgE1/svGxgAAAgAAAAACWAFKAA4A\
+GQAAMTQ2MzIeAhUjLgEiBgchIiY0NjMyFhUUBrN5OWtVMw8LouCiCwEcFyUlFxkjI5iyLFGATW6G\
+hm4kMCQkGBkjAAABAAAAAAC2AS0AGAAAEzIWFxYVFAcOASMwJyY1NDY1NCMuATU0NlYbGxAaMhlE\
+EAYBRxQbKC0BLQwRHTA9PB0tAwECCGsTDwEmHB4xAAAAAAIABQAAAY4B9QARACMAADcTNjMyFxYV\
+FAcDBiMiJyY1NCcTNjMyFxYVFAcDBiMiJyY1NJjNCwoGAwsFzAkLBAYLi80KCwYDCwXMCAwEBgsj\
+AcASAgUKBgz+PxEDBgwHBwHAEgIFCgYM/j8RAwYMBwABAAD/BgCCAPoAAwAANTMRI4KC+v4MAAAB\
+AAAAAACCAPoAAwAANTMVI4KC+voAAAABAAD/gwEsAAAAAwAAMSEVIQEs/tR9AAABAAAAAAEsAH0A\
+AwAANSEVIQEs/tR9fQABAAD+fgDrAYcAEwAAExcHFyYjIgYVFBcmNTQ2MzIXJzcpvWdsMjQfJjh4\
+NCUiIodkAYfl2c8uJB01NEtNIy0VvLQAAAEAAP8NAQAAwAAWAAA3DgIjIiY1NDYyFhUUBzI2NzYy\
+FwMnqwMZGhMrNyY4KRciMyECFQOWMDwBBwQpKB8gHhkdGyEsAgL+bxAAAAABAAD+DAFIAMAAJAAA\
+FwYjIiY1NDYzMhYVFAcyPwEGIyImNTQ2MzIWFRQHMjc2MhcDJ6soISs3JxscKRdBCzw2GCs3Jxsc\
+KRdILgIVA8UtxAwoKCAgHxkdGyLKDCkoHyAeGR0bTQIC/W4MAAABAAD+DAGPAcAANgAANwYjIiY1\
+NDYzMhYVFAcyPwEiDgEjIiY1NDYzMhYVFAcyNzYyFwEnEwYjIiY1NDYzMhYVFAcyN/YoHys3Jxsc\
+KRc/CzoBIBwTKzcnGxwpF0guARYD/vQtVSghKzcnGxwpF0ELPAwoKCAgHxkdGyLLCQQpKB8gHhkd\
+G00CAvxuDAEkDCgoICAfGR0bIgAAAAABAAD9DAHaAcAARQAAEwYjIiY1NDYzMhYVFAcyPwEGIyIm\
+NTQ2MzIWFRQHMj8BBiMiJjU0NjMyFhUUBzI/ASIOASMiJjU0NjIWFRQHMjc2MhcBJ6soISs3Jxsc\
+KRdBCzooISs3JxscKRdBCzooHys3JxscKRc/CzoBIBwTKzcmOCkXSC4CFQP+qS3+PAwoKCAgHxkd\
+GyLKDCgoICAfGR0bIsoMKCggIB8ZHRsiywkEKSgfIB4ZHRtNAgL7bgwAAAABAAD9DAIZAq4AVgAA\
+JQYjIiY1NDYzMhYVFAcyPwEGIyImNTQ2MzIWFRQHMj8BIg4BIyImNTQ2MzIWFRQHMjc2MhcBJxMG\
+IyImNTQ2MzIWFRQHMj8BBiMiJjU0NjMyFhUUBzI3AT8oISs3JxscKRdBCzYoHys3JxscKRc/CzQB\
+IBwTKzcnGxwpF0guARYD/motVSghKzcnGxwpF0ELOighKzcnGxwpF0ELNAwoKCAgHxkdGyLEDCgo\
+ICAfGR0bIscJBCkoHyAeGR0bTQIC+oAMASQMKCggIB8ZHRsiyAwoKCAgHxkdGyIAA//w/wYCJgD6\
+AAcADwATAAA2IiY0NjIWFAAiJjQ2MhYUBQEzAVAyIyMyIwGIMiMjMiP90gG4fv5HSyMyIyMy/s8j\
+MiMjMloB9P4MAAT/4f8GAwcA+gAHAA8AEwAXAAA2IiY0NjIWFAAiJjQ2MhYUBQEzATMBMwFBMiMj\
+MiMCdzIjIzIj/OMBuHv+R3kBuHv+R0sjMiMjMv7PIzIjIzJaAfT+DAH0/gwAAv+0/4gBfAEYABEA\
+OwAANxYzMjY3NjU0JyYjIgYHBhUUFyInBzMyFCsBIjQ7ARM2NTQjIg4DBwYmNzY3NjMyFhc+ATMy\
+FhUUBsUCBRIyDg0PAgQSNQsPJyoZLTQLC+ELC0toBgsIDA8LGQoFGwUxDxclIyQHHSYjHi1ZKAE1\
+JCAmJQUBMxwnJCkuIHoeHgEdEgwPBxQSLBAIDwlYEBkTGh4PNDBJawAAAf/b//YBvgEYAFMAADcG\
+BwYrASI/ATYmIyIGBwYmNz4DMzIXNjMyFz4BMzIWFRQPAQYVFDMyNz4FNzYWBw4CIyImNTQ/ATY1\
+NCMiDwEGBycGJj8BNjU0IyIHUAcIBAQ1DQ1CBAYIDRYkBRUEFBAiHxI3CyQkLQkLKRMZJAUvBAgB\
+BAULBwwDDQEGFQYSFy0fFRkFNAEVGwhBCA8mDQgEQwEVGwgVEQMBGqcNDxo5CAwJJRowEigoKBAY\
+IxoLD30LCQ4CAwkGDwUSAQkNCx8fGhYTDQ2IAgQOFKsWAQEBDwirAgQOFAAAAAH/fv9gAV4BuABB\
+AAAHIiY1NDYzMhYVFAcGFRQzMj4HNyMiNTQ7AT4BMzIWFRQGIyImNTQ3NjQjIg4HBzMyFCsBDgEy\
+IDAXExIXEgoZCxAPCw0KDg0UCjUTEUEUaTQgMBcTEhcSChkHDAoHCAUGAwYBNhMTPyF2oCYgGiIU\
+Dw4LBw0OBhETJSQ8N1UnFRNLXyYgGiIUDw4LBhwFCwoUDRoNHgYoxcEAAAH/2wAAARMBEwAqAAA3\
+PgEmIyIGDwEGBwYrASI/ATYmIyIGBwYmNz4BMzIWFzYzMhYVFCMiJjU00AUDAwUUJAk/BwgEBDUN\
+DUIEBggOFyIFFQQhOCIbHQQfJBogKw8b5wMHBCwYnBEDARqjDQ8aNQgMCTxAGg0nHhk3Fg0UAAAA\
+AQAAAAAA3AEYADEAADMiJjU0NjIWFRQHFjMyNjU0LgInJjU0NjMyFhUUBiMiJjU0NyYjIgYVFB4C\
+FxYVFFAeMhUaFxAMExYhCQsYBjo3LSI2FhANFgcPEQ8ZERIeBTArGxAWEAwSEBAWEgsPBw0EJTAj\
+KCQYEBgUDgMOExENCRMMEQMgK1UAAAH/5P/8AOcBDwA8AAAnBi4BPwEiBiMiJg4BBwYnJjc+ATce\
+ATMyNjMyFxYUDwEGFRQzMjYzMhcWNzYnIjU0NjMyFRQGKwEuASMiCgYLAQWwBR8MAxQMHwUPBAMI\
+DAsBDi8VIisECAULB54CAgENBhojDQgFByQSDSYoHh0YJQgOAwQIDQbBBgYCMgUNCwkSHjACAQYL\
+AQERB6cGAgMDGgoODQMdDhU3Hi8DGAAF/7T/iAVLARgAEAAiALUAxgDXAAAlFjMyNjc2NCcmIyIG\
+BwYVFAUWMzI2NzY1NCcmIyIGBwYVFBciJwczMhQrASI0OwETNjU0IyIOAwcGJjc2NzYzMhYXPgEz\
+Mhc+ATMyFhc+ATMyFz4BMzIWFz4BMzIXPgEzMhYXPgEzMhYVFAYjIicHMzIUKwEiNDsBEzY1NCMi\
+BgcVFAYjIicHMzIUKwEiNDsBEzY1NCMiBgcVFAYjIicHMzIUKwEiNDsBEzY1NCMiBgcVFAYlFjMy\
+Njc2NCcmIyIGBwYVFAUWMzI2NzY0JyYjIgYHBhUUAgoCBRIyDg0PAgQSNQsP/ssCBRIyDg0PAgQS\
+NQsPExYZLTQLC+ELC0toBgsIDA8LGQoFGwUxDxclIyQHHSYjLxMSIhsjJAcdJiMvExIiGyMkBx0m\
+Iy8TEiIbIyQHHSYjHi1rSRYZLTQLC+ELC0toBgsOFBRrSRYZLTQLC+ELC0toBgsOFBRrSRYZLTQL\
+C+ELC0toBgsOFBRrA4MCBRIyDg0PAgQSNQsP/ssCBRIyDg0PAgQSNQsPKAE1JCFKBQEzHCckKQYB\
+NSQgJiUFATMcJyQpLiB6Hh4BHRIMDwcUEiwQCA8JWBAZExoeDzIcFhMaHg8yHBYTGh4PMhwWExoe\
+DzQwR20geh4eAR0SDA8YIwFHbSB6Hh4BHRIMDxgjAUdtIHoeHgEdEgwPGCMBR20oATUkIUoFATMc\
+JyQpBgE1JCFKBQEzHCckKQAE/7T/iAQGARgAbwCAAJEAowAAISInBzMyFCsBIjQ7ARM2NTQjIgYH\
+FRQGIyInBzMyFCsBIjQ7ARM2NTQjIgYHFRQGIyInBzMyFCsBIjQ7ARM2NTQjIg4DBwYmNzY3NjMy\
+Fhc+ATMyFz4BMzIWFz4BMzIXPgEzMhYXPgEzMhYVFAYnFjMyNjc2NCcmIyIGBwYVFAUWMzI2NzY0\
+JyYjIgYHBhUUBRYzMjY3NjU0JyYjIgYHBhUUA1IWGS00CwvhCwtLaAYLDhQUa0kWGS00CwvhCwtL\
+aAYLDhQUa0kWGS00CwvhCwtLaAYLCAwPCxkKBRsFMQ8XJSMkBx0mIy8TEiIbIyQHHSYjLxMSIhsj\
+JAcdJiMeLWtMAgUSMg4NDwIEEjULD/7LAgUSMg4NDwIEEjULD/7LAgUSMg4NDwIEEjULDyB6Hh4B\
+HRIMDxgjAUdtIHoeHgEdEgwPGCMBR20geh4eAR0SDA8HFBIsEAgPCVgQGRMaHg8yHBYTGh4PMhwW\
+ExoeDzQwR20oATUkIUoFATMcJyQpBgE1JCFKBQEzHCckKQYBNSQgJiUFATMcJyQpAAAD/7T/iALB\
+ARgATABeAG8AADMiJwczMhQrASI0OwETNjU0IyIOAwcGJjc2NzYzMhYXPgEzMhc+ATMyFhc+ATMy\
+FhUUBiMiJwczMhQrASI0OwETNjU0IyIGBxUUBicWMzI2NzY1NCcmIyIGBwYVFAUWMzI2NzY0JyYj\
+IgYHBhUUyBYZLTQLC+ELC0toBgsIDA8LGQoFGwUxDxclIyQHHSYjLxMSIhsjJAcdJiMeLWtJFhkt\
+NAsL4QsLS2gGCw4UFGtMAgUSMg4NDwIEEjULDwFVAgUSMg4NDwIEEjULDyB6Hh4BHRIMDwcUEiwQ\
+CA8JWBAZExoeDzIcFhMaHg80MEdtIHoeHgEdEgwPGCMBR20oATUkICYlBQEzHCckKQYBNSQhSgUB\
+MxwnJCkAAAAC/9v/iAMrARgAcgCDAAAlNjc2MzIWFz4BMzIWFRQGIyInBzMyFCsBIjQ7ARM2NTQj\
+Ig4DBw4BIyImNTQ/ATY1NCMiDwEGBycGJj8BNjU0IyIPAQYHBisBIj8BNiYjIgYHBiY3PgMzMhc2\
+MzIXPgEzMhYVFA8BBhUUMzI3PgEXFjMyNjc2NCcmIyIGBwYVFAGtMRQYIiMkBx0mIx4ta0kWGS00\
+CwvhCwtLaAYLBw8SDhgIGjwtFRkFNAEVGwhBCA8mDQgEQwEVGwhEBwgEBDUNDUIEBggNFiQFFQQU\
+ECIfEjcLJCQtCQspExkkBS8ECAEEDyXRAgUSMg4NDwIEEjULD3dsGB0TGh4PNDBHbSB6Hh4BHRIM\
+Dw0fGzUQNjgWEw0NiAIEDhSrFgEBAQ8IqwIEDhSsEQMBGqcNDxo5CAwJJRowEigoKBAYIxoLD30L\
+CQ4CCDU5ATUkIUoFATMcJyQpAAAAAAL/2/9gAxkBuABTAJUAADcGBwYrASI/ATYmIyIGBwYmNz4D\
+MzIXNjMyFz4BMzIWFRQPAQYVFDMyNz4FNzYWBw4CIyImNTQ/ATY1NCMiDwEGBycGJj8BNjU0IyIH\
+EyImNTQ2MzIWFRQHBhUUMzI+BzcjIjU0OwE+ATMyFhUUBiMiJjU0NzY0IyIOBwczMhQrAQ4BUAcI\
+BAQ1DQ1CBAYIDRYkBRUEFBAiHxI3CyQkLQkLKRMZJAUvBAgBBAULBwwDDQEGFQYSFy0fFRkFNAEV\
+GwhBCA8mDQgEQwEVGwj1IDAXExIXEgoZCxAPCw0KDg0UCjUTEUEUaTQgMBcTEhcSChkHDAoHCAUG\
+AwYBNhQUPyF2FREDARqnDQ8aOQgMCSUaMBIoKCgQGCMaCw99CwkOAgMJBg8FEgEJDQsfHxoWEw0N\
+iAIEDhSrFgEBAQ8IqwIEDhT+nyYgGiIUDw4LBw0OBhETJSQ8N1UnFRNLXyYgGiIUDw4LBhwFCwoU\
+DRoNHgYoxcEAAAAB/37/YAJpAbgAdAAAJSMOASMiJjU0NjMyFhUUBwYVFDMyPgc3IyI1NDsBPgEz\
+MhYVFAYjIiY1NDc2NCMiBwYHFz4BMzIWFRQGIyImNTQ3NjQjIg4HBzMyFCsBDgEjIiY1NDYzMhYV\
+FAcGFRQzMj4HAV+bIXZfIDAXExIXEgoZCxAPCw0KDg0UCjUTEUEUaTQgMBcTEhcSChkmFwMBmxRp\
+NCAwFxMSFxIKGQcMCgcIBQYDBgE2FBQ/IXZfIDAXExIXEgoZCxAPCw0KDw0U5sXBJiAaIhQPDgsH\
+DQ4GERMlJDw3VScVE0tfJiAaIhQPDgsGHHMMBgFLXyYgGiIUDw4LBhwFCwoUDRoNHgYoxcEmIBoi\
+FA8OCwcNDgYREyUkPDdVAAAAAf9+/2ADdAG4AKoAABMzPgEzMhYVFAYjIiY1NDc2NCMiBwYHFz4B\
+MzIWFRQGIyImNTQ3NjQjIg4HBzMyFCsBDgEjIiY1NDYzMhYVFAcGFRQzMj4HNyMOASMiJjU0NjMy\
+FhUUBwYVFDMyPgc3Iw4BIyImNTQ2MzIWFRQHBhUUMzI+BzcjIjU0OwE+ATMyFhUUBiMiJjU0NzY0\
+IyIOAgcGzJwUaTQgMBcTEhcSChkmFwMBmxRpNCAwFxMSFxIKGQcMCgcIBQYDBgE2ExM/IXZfIDAX\
+ExIXEgoZCxAPCw0KDw0UCpshdl8gMBcTEhcSChkLEA8LDQoODRUKmyF2XyAwFxMSFxIKGQsQDwsN\
+Cg4NFAo1ExFBFGk0IDAXExIXEgoZDhUOCAYCAQ5LXyYgGiIUDw4LBhxzDAYBS18mIBoiFA8OCwYc\
+BQsKFA0aDR4GKMXBJiAaIhQPDgsHDQ4GERMlJDw3VSfFwSYgGiIUDw4LBw0OBhETJSQ7OFUnxcEm\
+IBoiFA8OCwcNDgYREyUkPDdVJxUTS18mIBoiFA8OCwYcEyciGwoAAf9+/2AEgAG4AOAAAAEXPgEz\
+MhYVFAYjIiY1NDc2NCMiBwYHFz4BMzIWFRQGIyImNTQ3NjQjIg4HBzMyFCsBDgEjIiY1NDYzMhYV\
+FAcGFRQzMj4HNyMOASMiJjU0NjMyFhUUBwYVFDMyPgc3Iw4BIyImNTQ2MzIWFRQHBhUUMzI+Bzcj\
+DgEjIiY1NDYzMhYVFAcGFRQzMj4HNyMiNTQ7AT4BMzIWFRQGIyImNTQ3NjQjIgcGBxc+ATMyFhUU\
+BiMiJjU0NzY0IyIOBwHYnBRpNCAwFxMSFxIKGSYXAwGbFGk0IDAXExIXEgoZBwwKBwgFBgMGATYT\
+Ez8hdl8gMBcTEhcSChkLEA8LDQoPDRQKmyF2XyAwFxMSFxIKGQsQDwsNCg4NFAqbIXZfIDAXExIX\
+EgoZCxAPCw0KDw0UCpshdl8gMBcTEhcSChkLEA8LDQoODRQKNRMRQRRpNCAwFxMSFxIKGSYXAwGb\
+FGk0IDAXExIXEgoZBwwKBwgFBgMGAQ8BS18mIBoiFA8OCwYccwwGAUtfJiAaIhQPDgsGHAULChQN\
+Gg0eBijFwSYgGiIUDw4LBw0OBhETJSQ8N1UnxcEmIBoiFA8OCwcNDgYREyUjPDdWJ8XBJiAaIhQP\
+DgsHDQ4GERMlJDw3VSfFwSYgGiIUDw4LBw0OBhETJSQ8N1UnFRNLXyYgGiIUDw4LBhxzDAYBS18m\
+IBoiFA8OCwYcBQoLEw4ZDh0AAAAAAwAA/2AC3wG4ADYAcQC2AAAzIiY1NDYzMhYVFAcWMzI2NTQu\
+AycuAjU0NjMyFhUUBiMiJjU0NyYjIgYVFB4CFx4BFRQlBi4BPwEmIyIGIyIOAQcGJyY3PgE3FjMy\
+NjMyFxYUDwEGFRQeARcWPgEnLgE1NDYzMhUUBisBLgEjIgUiJjU0NjMyFhUUBw4BHgEVFBYyPgc3\
+IyI1NDsBPgEzMhYVFAYjIiY1NDc2NCMiDgcHMzIUKwEOAVAeMhIMDhcMBhgWIQMMBRcDFBgUNy0i\
+NhYQDRYFEA4PGRESHgUbFwEmBgsBBa4KFAUaCQUQEgUQBQMHCwwBMRogKAcCFAsHmgQaKg0FCQUC\
+AyQUDCYrIAYXNQkT/rQgMBcTEhcSBAEBAgYWEA8LDQoODRQKNRMRQRRpNCAwFxMSFxIKGQcMCgcI\
+BQYDBgE2ExM/IXYrGxAWDQsYDBIWEgkMDQQOAg0SHg8jLSQYEBgUDgYKFBENCRMMEQMSHxVaAwQI\
+DQa/BQEYHwQNCwgTHi4EAwgCAREHowUFBwMHCwMEDQYICBQLEzceKgINsiYgGiIUDw4LAgUEBgMI\
+BgYREyUkPDdVJxUTS18mIBoiFA8OCwYcBQsKFA0aDR4GKMXBAAIABf/7AfwBmgAJACwAAAEPAQYV\
+FDMyNjcHDgEjIiY1ND8BIzUzPwEHNzIVNjMyFhUUBiImNTQ3Bg8BIwE2fzkCFBhEEgwpMh8iIgM3\
+aXMWWSaYGSA1GB0YIBYJNxBCSAEJB7gIAxUXDygZFCQaCwuzIEoueAotKRwVEhsRDhITDyfYAAEA\
+AP/9AbUA1AAvAAAlMjU0JwYiJjU+ATMyFhUUBwYjIi8BJiMiFRQXNjMyFhUUBgciJyY1NDc2MxYf\
+ARYBcC0aEBwUARcJJC0pFx4mHqIaES4ZEA8NFRQNHRgcKBYiKheiHDE5IBMQFg0OFjM1MyQVFXoS\
+OiASEBcODxICGiItMyQTAhB6EwABAAD/zQG1AQMANgAAFyImNTQ3NjMWHwE1MxUXFjMyNjU0JwYj\
+IjU+ATMyFhUUBwYjIi8BFSM1JyYjIgYVFBc2MzIVFE0gLSgWICgXLh5cIg8VHBYUEhwBDwkgLSkX\
+HCQeLh5cIA8WHBUUExwDQyYzJBMCECRpgEcXKRwnEAwbDxVCJjMkFRUkbINHFigeJw8MHSAAAQAN\
+AAACRQDgAAsAADcnNxc3FzcXBycHJyIVjmV4ak0Wkml0aS4ZmXx8fFQXoXx8fAAAAAEADf/LAkUB\
+EQATAAAlBycHJzcXNzUzFzcXNxcHJwcVIwEWQGlLFY5lFhsBRmpPFJJpGRtGRnxOGZl8F5Z7SnxV\
+GKF8G5YAAAEAAAAAARgBGAALAAAzNSM1MzUzFTMVIxV7e3sie3t7Int7InsAAAABAAD+jgE2AAAA\
+CgAAETU+BDczFAYkMUw0NA8ewP6OPAcPKz1vSY7aAAAAAAH//wAAAS0AoAAdAAA3PgIzMh4BFxYz\
+Mjc2FgcOAiMiLgEnJiMiBwYmAQkQKRwYJiYPCQoeGAQOAggRKRwYJCUSBwgdHQUNRhkhICExCgYk\
+BgcHGSIfITEKBCMGCAAAAAEAAAAAASwBLAAHAAAxESERIzUjFQEsI+YBLP7UtLQAAQAAAAAA+gHC\
+AAYAADMDMxsBMwNpaShVVShpAcL+mAFo/j4AAgAAAAAAyADIAAcADwAANjI2NCYiBhQWIiY0NjIW\
+FEU+LCw+LHRSOztSOxksPiwsPkU7Ujs7UgAB/zgAAADIAMgACwAAIzQ2MhYVIzQmIgYVyHakdh5g\
+lGBSdnZSSmBgSgAAAAIAAAAAALQBLAAHABUAADYyNjQmIgYUFzUuATU0NjIWFRQGBxVLHhsbHhsY\
+HSs3RjcrHXg1NjU1Nq1kCDUnKDw8KCc1CGQAAAIAAAAAAMgBLAAPAB8AADcuATU0NjIWFRQGBx0B\
+IzU3PgE1NCYiBhUUFhc9ATMVVCQwO1I7MCQgIBkiLD4sIhkgZgU4JSk7OyklOAUBZWUZBikbHyws\
+HxspBgFJSQAAAAQAAP/8A/QCfwCDAI0AmQCjAAA3PgE1NCcuATU0PgI/Ag4BFRQzMjcXDgEjIiY1\
+ND4CMzIWFRQGIyImJzceATMyNTQuAicHBhUUHgIVFAYPAR4COwEyNyY1NDc2MzIWFRQHBgceATMy\
+NjU0NjcuAj0BHgEVFAYjIicGIyIuAScOASImJyYjIg4BBwYjIjU0NgUiJjQ2MzIWFAYlPgE1NCcm\
+IyIGFRQFNjU0JicOARUUmDZFBQNSAwQFAQI9YmogHh0aFSklHi0fPnBIfXIyLhw2EhgRFBQzDBxA\
+LRsJHSIdHxAQDikfDA0RIiEcIkwbIwodPBMgGxYxQ10ggV66tFFCQSMkPCEzFg4sKCAbHBoSChQZ\
+BlohCjADXAsTEwsMEhH+DzMkBAUVHC4BPFoXEj8wWhg/IAoMCHQiBQ4ODAQEpQRjLCM8DUIvJhoh\
+R0QsUTcwNTArDh4RMgoaIhoCQiQbHjYhLBUmTxQUCikaHjUoNSUuKyAhEzkwFxIqGVJfGSxZNAYB\
+PLFrRl85OhUUEiYZFiAtCxUEOgcNOE8SGBISGBKALy8bBw4SOSsedAWBIkkVHVhEPwAAAgAMAAoB\
+0wHPAAoAjwAAJTQmIyIGFBYzMjYnDgEjIiY0NjMyFhc2NTQnJiMiJjQ2MzIXHgEXFjMyNTQnLgE1\
+NDYzMhYVFAYHFDMyNz4BNzYzMhYVFAYjIgYHBhUUMzI2MzIWFAYjIiYjIgYVFBceARcWFRQGIyIn\
+LgEnJiMiFRQWFRQGIyImNDY1NCMiBw4BBwYjIiY1NDc2MzI3NjU0ARYYDxAVFg8QF4kZJg4ZGxoZ\
+DSoYJQwUGB0cGRcSEAsDFAsSFAECJx4UEhsjARYQDREBDQwaEx4bEhsVDRAhGysOHBsdGQ8nFBcS\
+CxQ4DQ4ZFxMUDAETEQoSKiATEh0mFw0OEgMREgwUGg0MFiMSDe4QFBMiFhUKASUZKhslAgMWDgoT\
+HCgdDAs+Fg0YDQgXJw8XGhsWESMZKhATOA8OGxQRIwYLDg8WJx0oGiYIChYKEgMLDRkTGw4LORUP\
+Hx4wExQYGiQxFyQOEj0MBxYUGA4NEg0MGgAAAAIAAP8GAfQA+gAHAA8AADYUFjI2NCYiAjQ2MhYU\
+BiIteKp4eKqlktCSktBVqnh4qnj+y9CSktCSAAAAAAEAAP8GAdIA+gAhAAAlFhUUBwYjIicmIyIG\
+FBYzMjc2MzIXFhUUBwYjIiY0NjMyAc8CCwUGDQozd1V4eFV4MgYSBwMMA0CVaJKSaJSCBgQPBgMO\
+X3iqeF8NAgcMBgZ4ktCSAAABAAD/xAB4ADwABwAAFiImNDYyFhRVMiMjMiM8IzIjIzIAAAABAAD+\
+ogAtAV4AAwAAExEjES0tAV79RAK8AAAAAgAA/RIBSgC+AAMADwAANxUhNSUzFSE1MxEjESEVIx4B\
+Dv7UHgEOHh7+8h5GjIx4MjL8VAJiMgABAAAAAAJWAXIACwAANTcXNxc3FwEnBycHiVRWUq8i/vxU\
+VlM1Qblzc3HpFv6kdHRwRwAAAf/oAL0BEgE3ABcAACY+ATMyFjI3NjMyFRQOASMiJiIHBiMiNRgw\
+IRwUVSgUAwcOMCEcFFUoFAQHDfYwET0UAwcMMBE9FAQIAAAAAAIAAP+JAcIAiwALABMAAAU0JiMi\
+BhUUFjMyPgEUBiImNDYyAVFaNiA0XTchL3F9yH19yBQ0TycfNEwjdGxLS2xLAAAAAgAA/2wBSAKo\
+AA0AHAAAJSYjIgYVFBcWMzI2NTQTMxEUBiMiJyY1NDYzMhcBJA0lPJcGCyY8lwIdfkhPJA9+SEAk\
+RBdhKwoJF2ErCQJu/W5DZ0MeHUNnLQAAAAABAAD/eQFAAqgADQAAATMRFAYjIiY1NDYzMhcBIh55\
+WTI8elgxHwKo/YBGaTgnRWocAAAAAQAA/3kCCgKoABoAAAERFAYjIiY1NDYzMhcRNTMeBBUUBzY1\
+NAFAeVkyPHpYMR8eBi45OCUyEgG5/m9GaTgnRWocAU7vNVxHTnVJSHJBSe8AAAAAAgAA/3kCCwKo\
+AB8AKAAAJTURMx4EFRQHFhUUBzY1NCYjERQGIyImNTQ2MzITHgEXNDY1NCYBIh4JMDg2IxUWIgV2\
+OHlZMjx6WDE9DoUbAXZr5gFXJEc7QlgzJTIwJC5BGSFfnv7WRmk4J0VqAXY7ojUDDgNfnwAAAQAA\
+/84AZAAyAAcAABYiJjQ2MhYURyodHSodMh0qHR0qAAAAAAAOAK4AAQAAAAAAAACDAQgAAQAAAAAA\
+AQAHAZwAAQAAAAAAAgAHAbQAAQAAAAAAAwAfAfwAAQAAAAAABAAHAiwAAQAAAAAABQAIAkYAAQAA\
+AAAABgAHAl8AAwABBAkAAAEGAAAAAwABBAkAAQAOAYwAAwABBAkAAgAOAaQAAwABBAkAAwA+AbwA\
+AwABBAkABAAOAhwAAwABBAkABQAQAjQAAwABBAkABgAOAk8AQwBvAHAAeQByAGkAZwBoAHQAIABc\
+ADIANQAxACAAMgAwADEAOAAtADIAMAAyADQAIABKAGUAYQBuAC0ARgByAGEAbgBjAG8AaQBzACAA\
+TQBvAGkAbgBlAC4AIABUAGgAaQBzACAAZgBvAG4AdAAgAGkAcwAgAGwAaQBjAGUAbgBzAGUAZAAg\
+AHUAbgBkAGUAcgAgAHQAaABlACAAUwBJAEwAIABPAHAAZQBuACAARgBvAG4AdAAgAEwAaQBjAGUA\
+bgBzAGUAIABcACgAaAB0AHQAcAA6AC8ALwBzAGMAcgBpAHAAdABzAC4AcwBpAGwALgBvAHIAZwAv\
+AE8ARgBMAFwAKQAuAABDb3B5cmlnaHQgXDI1MSAyMDE4LTIwMjQgSmVhbi1GcmFuY29pcyBNb2lu\
+ZS4gVGhpcyBmb250IGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBTSUwgT3BlbiBGb250IExpY2Vuc2Ug\
+XChodHRwOi8vc2NyaXB0cy5zaWwub3JnL09GTFwpLgAAYQBiAGMAMgBzAHYAZwAAYWJjMnN2ZwAA\
+UgBlAGcAdQBsAGEAcgAAUmVndWxhcgAARgBvAG4AdABGAG8AcgBnAGUAIAA6ACAAYQBiAGMAMgBz\
+AHYAZwAgADoAIAAzADAALQA0AC0AMgAwADIANAAARm9udEZvcmdlIDogYWJjMnN2ZyA6IDMwLTQt\
+MjAyNAAAYQBiAGMAMgBzAHYAZwAAYWJjMnN2ZwAAVgBlAHIAcwBpAG8AbgAgAABWZXJzaW9uIAAA\
+YQBiAGMAMgBzAHYAZwAAYWJjMnN2ZwAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
+AACOAAAAAQACAQIAAwEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEY\
+ARkBGgEbARwBHQEeAR8BIAEhASIBIwEkASUBJgEnASgBKQEqASsBLAEtAS4BLwEwATEBMgEzATQB\
+NQE2ATcBOAE5AToBOwE8AT0BPgE/AUABQQFCAUMBRAFFAUYBRwFIAUkBSgFLAUwBTQFOAU8BUAFR\
+AVIBUwFUAVUBVgFXAVgBWQFaAVsBXAFdAV4BXwFgAWEBYgFjAWQBZQFmAWcBaAFpAWoBawFsAW0B\
+bgFvAXABcQFyAXMBdAF1AXYBdwF4AXkBegF7AXwBfQF+AX8BgAGBAYIBgwGEAYUBhgGHAYgBiQGK\
+AYsGLm5vZGVmB3VuaUUwMDAHdW5pRTAzMAd1bmlFMDM4B3VuaUUwMzkHdW5pRTA0Mwd1bmlFMDQ0\
+B3VuaUUwNDUHdW5pRTA0Ngd1bmlFMDQ3B3VuaUUwNDgHdW5pRTA1MAd1bmlFMDVDB3VuaUUwNjIH\
+dW5pRTA2OQd1bmlFMDdBB3VuaUUwN0IHdW5pRTA3Qwd1bmlFMDdEB3VuaUUwN0UHdW5pRTA4MAd1\
+bmlFMDgxB3VuaUUwODIHdW5pRTA4Mwd1bmlFMDg0B3VuaUUwODUHdW5pRTA4Ngd1bmlFMDg3B3Vu\
+aUUwODgHdW5pRTA4OQd1bmlFMDhBB3VuaUUwOEMHdW5pRTA5NAd1bmlFMDk1B3VuaUUwQTAHdW5p\
+RTBBMQd1bmlFMEEyB3VuaUUwQTMHdW5pRTBBNAd1bmlFMEE5B3VuaUUwQjMHdW5pRTEwMQd1bmlF\
+MUI5B3VuaUUxQkIHdW5pRTFFNwd1bmlFMUYyB3VuaUUxRjQHdW5pRTFGNwd1bmlFMUY5B3VuaUUx\
+RkMHdW5pRTFGRQd1bmlFMUZGB3VuaUUyMDAHdW5pRTI0MAd1bmlFMjQxB3VuaUUyNDIHdW5pRTI0\
+Mwd1bmlFMjQ0B3VuaUUyNDUHdW5pRTI0Ngd1bmlFMjQ3B3VuaUUyNDgHdW5pRTI0OQd1bmlFMjYw\
+B3VuaUUyNjEHdW5pRTI2Mgd1bmlFMjYzB3VuaUUyNjQHdW5pRTI2YQd1bmlFMjZiB3VuaUUyODAH\
+dW5pRTI4MQd1bmlFMjgyB3VuaUUyODMHdW5pRTRBMAd1bmlFNEEyB3VuaUU0QTQHdW5pRTRBOAd1\
+bmlFNEFDB3VuaUU0QzAHdW5pRTRDRQd1bmlFNEQxB3VuaUU0RTEHdW5pRTRFMgd1bmlFNEUzB3Vu\
+aUU0RTQHdW5pRTRFNQd1bmlFNEU2B3VuaUU0RTcHdW5pRTRFOAd1bmlFNEU5B3VuaUU0RUEHdW5p\
+RTUwMAd1bmlFNTAxB3VuaUU1MjAHdW5pRTUyMQd1bmlFNTIyB3VuaUU1MjMHdW5pRTUyNAd1bmlF\
+NTI1B3VuaUU1MjkHdW5pRTUyQQd1bmlFNTJCB3VuaUU1MkMHdW5pRTUyRAd1bmlFNTJGB3VuaUU1\
+MzAHdW5pRTUzMQd1bmlFNTM5B3VuaUU1NjYHdW5pRTU2Nwd1bmlFNTY5B3VuaUU1NkMHdW5pRTU2\
+RAd1bmlFNTgyB3VuaUU1RDAHdW5pRTVFMgd1bmlFNjEwB3VuaUU2MTIHdW5pRTYxNAd1bmlFNjE4\
+B3VuaUU2MjQHdW5pRTYzMAd1bmlFNjUwB3VuaUU2NTUHdW5pRTkxMQd1bmlFOTE1B3VuaUU5MjAH\
+dW5pRTkyNQd1bmlFOTVEB3VuaUVBMDIHdW5pRUFBNAd1bmlFQ0EyB3VuaUVDQTMHdW5pRUNBNQd1\
+bmlFQ0E3B3VuaUVDQTkHdW5pRUNCNwAAAAAAAAH//wACAAEAAAAAAAAADAAUAAQAAAACAAAAAQAA\
+AAEAAAAAAAEAAAAA4UXjegAAAADRlyIXAAAAAOJWUs0=\
") format("truetype")'
Index: font/abc2svg.sfd
==================================================================
--- font/abc2svg.sfd
+++ font/abc2svg.sfd
@@ -18,11 +18,11 @@
FSType: 0
OS2Version: 0
OS2_WeightWidthSlopeOnly: 0
OS2_UseTypoMetrics: 0
CreationTime: 1433498007
-ModificationTime: 1705513399
+ModificationTime: 1714463309
PfmFamily: 17
TTFWeight: 400
TTFWidth: 5
LineGap: 92
VLineGap: 92
@@ -48,15 +48,15 @@
UnicodeInterp: none
NameList: Adobe Glyph List
DisplaySize: -96
AntiAlias: 1
FitToEm: 1
-WinInfo: 32 8 2
+WinInfo: 104 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
+BeginChars: 139 139
StartChar: .nodef
Encoding: 0 0 0
Width: 400
Flags: M
@@ -179,13 +179,28 @@
78 420 100 398 100 370 c 7
100 342 78 320 50 320 c 7
EndSplineSet
Validated: 1
EndChar
+
+StartChar: uniE044
+Encoding: 7 57412 7
+Width: 100
+Flags: HMW
+LayerCount: 2
+Fore
+SplineSet
+50 -50 m 7
+ 22 -50 0 -28 0 0 c 7
+ 0 28 22 50 50 50 c 7
+ 78 50 100 28 100 0 c 7
+ 100 -28 78 -50 50 -50 c 7
+EndSplineSet
+EndChar
StartChar: uniE045
-Encoding: 7 57413 7
+Encoding: 8 57413 8
Width: 1059
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -239,11 +254,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE046
-Encoding: 8 57414 8
+Encoding: 9 57414 9
Width: 1061
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -290,11 +305,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE047
-Encoding: 9 57415 9
+Encoding: 10 57415 10
Width: 480
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -340,11 +355,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE048
-Encoding: 10 57416 10
+Encoding: 11 57416 11
Width: 954
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -384,11 +399,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE050
-Encoding: 11 57424 11
+Encoding: 12 57424 12
Width: 779
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -440,11 +455,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE05C
-Encoding: 12 57436 12
+Encoding: 13 57436 13
Width: 722
Flags: MW
LayerCount: 2
Fore
SplineSet
@@ -506,11 +521,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE062
-Encoding: 13 57442 13
+Encoding: 14 57442 14
Width: 703
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -544,11 +559,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE069
-Encoding: 14 57449 14
+Encoding: 15 57449 15
Width: 470
Flags: W
VStem: 0 150<-250 250> 220 150<-250 250>
LayerCount: 2
Fore
@@ -566,11 +581,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE07A
-Encoding: 15 57466 15
+Encoding: 16 57466 16
Width: 779
Flags: MW
LayerCount: 2
Fore
SplineSet
@@ -626,11 +641,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE07B
-Encoding: 16 57467 16
+Encoding: 17 57467 17
Width: 782
Flags: MW
LayerCount: 2
Fore
SplineSet
@@ -692,11 +707,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE07C
-Encoding: 17 57468 17
+Encoding: 18 57468 18
Width: 807
Flags: MW
LayerCount: 2
Fore
SplineSet
@@ -730,11 +745,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE07D
-Encoding: 18 57469 18
+Encoding: 19 57469 19
Width: 200
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -760,11 +775,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE07E
-Encoding: 19 57470 19
+Encoding: 20 57470 20
Width: 360
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -821,11 +836,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE080
-Encoding: 20 57472 20
+Encoding: 21 57472 21
Width: 430
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -842,11 +857,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE081
-Encoding: 21 57473 21
+Encoding: 22 57473 22
Width: 290
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -864,11 +879,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE082
-Encoding: 22 57474 22
+Encoding: 23 57474 23
Width: 400
Flags: HMW
HStem: -250 500
VStem: 0 400
LayerCount: 2
@@ -899,11 +914,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE083
-Encoding: 23 57475 23
+Encoding: 24 57475 24
Width: 380
Flags: HMW
HStem: -250 500
LayerCount: 2
Fore
@@ -936,11 +951,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE084
-Encoding: 24 57476 24
+Encoding: 25 57476 25
Width: 400
Flags: HMW
HStem: -250 500
VStem: 0 400
LayerCount: 2
@@ -967,11 +982,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE085
-Encoding: 25 57477 25
+Encoding: 26 57477 26
Width: 400
Flags: HMW
HStem: -250 500
LayerCount: 2
Fore
@@ -1002,11 +1017,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE086
-Encoding: 26 57478 26
+Encoding: 27 57478 27
Width: 385
Flags: HMW
HStem: -250 500
LayerCount: 2
Fore
@@ -1032,11 +1047,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE087
-Encoding: 27 57479 27
+Encoding: 28 57479 28
Width: 400
Flags: HMW
HStem: -250 500
LayerCount: 2
Fore
@@ -1055,11 +1070,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE088
-Encoding: 28 57480 28
+Encoding: 29 57480 29
Width: 400
Flags: HMW
HStem: -250 500
LayerCount: 2
Fore
@@ -1086,11 +1101,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE089
-Encoding: 29 57481 29
+Encoding: 30 57481 30
Width: 385
Flags: HMW
HStem: -250 500
LayerCount: 2
Fore
@@ -1116,11 +1131,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE08A
-Encoding: 30 57482 30
+Encoding: 31 57482 31
Width: 409
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -1144,55 +1159,10 @@
379 77 354 30 315 30 c 0
270 30 242 69 242 104 c 1
244 130 265 164 302 164 c 2
303 164 l 1
EndSplineSet
-Validated: 1
-EndChar
-
-StartChar: uniE08B
-Encoding: 31 57483 31
-Width: 408
-Flags: MW
-LayerCount: 2
-Fore
-SplineSet
-303 164 m 1
- 311 163 315 162 321 161 c 1
- 324 165 325 170 325 174 c 0
- 325 202 273 228 234 228 c 0
- 226 228 223 226 215 224 c 1
- 215 -213 l 1
- 224 -216 229 -217 239 -217 c 0
- 265 -217 294 -207 322 -183 c 0
- 350 -159 369 -119 392 -71 c 1
- 392 -74 410 -77 409 -80 c 0
- 377 -180 338 -244 216 -246 c 1
- 215 -246 l 1
- 215 -350 l 1
- 180 -350 l 1
- 180 -243 l 1
- 141 -237 103 -218 73 -189 c 0
- 34 -151 13 -98 10 -30 c 0
- 10 -26 9 13 9 17 c 0
- 9 164 76 230 180 245 c 1
- 180 350 l 1
- 215 350 l 1
- 215 248 l 1
- 221 248 222 249 228 249 c 0
- 280 249 325 222 345 199 c 0
- 365 176 379 150 379 124 c 0
- 379 77 354 30 315 30 c 0
- 270 30 242 69 242 104 c 1
- 244 130 265 164 302 164 c 2
- 303 164 l 1
-180 -197 m 1
- 180 209 l 1
- 144 182 119 122 119 18 c 0
- 119 -58 126 -133 158 -175 c 0
- 165 -184 172 -191 180 -197 c 1
-EndSplineSet
Validated: 1
EndChar
StartChar: uniE08C
Encoding: 32 57484 32
@@ -3996,17 +3966,16 @@
Width: 310
Flags: HMW
LayerCount: 2
Fore
SplineSet
-0 0 m 1
- 0 60 l 1
- 93 79 229 114 280 370 c 1
- 310 370 l 1
- 310 175 152 13 0 0 c 1
+0 -370 m 5
+ 0 -310 l 5
+ 93 -291 229 -256 280 0 c 5
+ 310 0 l 5
+ 310 -195 152 -357 0 -370 c 5
EndSplineSet
-Validated: 1
EndChar
StartChar: uniE5E2
Encoding: 117 58850 117
Width: 300
@@ -4314,38 +4283,12 @@
179 219 166 230 141 230 c 0xcb40
EndSplineSet
Validated: 1
EndChar
-StartChar: uniE910
-Encoding: 126 59664 126
-Width: 500
-Flags: HMW
-LayerCount: 2
-Fore
-SplineSet
-45 0 m 28
- 45 -113 137 -205 250 -205 c 28
- 363 -205 455 -113 455 0 c 28
- 455 113 363 205 250 205 c 28
- 137 205 45 113 45 0 c 28
-0 0 m 28
- 0 139 111 250 250 250 c 28
- 388 250 500 139 500 0 c 28
- 500 -138 389 -250 250 -250 c 28
- 112 -250 0 -138 0 0 c 28
-250 -60 m 7
- 217 -60 190 -33 190 0 c 7
- 190 33 217 60 250 60 c 7
- 283 60 310 33 310 0 c 7
- 310 -33 283 -60 250 -60 c 7
-EndSplineSet
-Validated: 1
-EndChar
-
StartChar: uniE911
-Encoding: 127 59665 127
+Encoding: 126 59665 126
Width: 500
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4361,75 +4304,12 @@
112 -250 0 -138 0 0 c 24
EndSplineSet
Validated: 1
EndChar
-StartChar: uniE912
-Encoding: 128 59666 128
-Width: 500
-Flags: HMW
-LayerCount: 2
-Fore
-SplineSet
-228 350 m 5
- 273 350 l 5
- 273 250 l 5
- 400 237 500 131 500 0 c 4
- 500 -130 401 -237 273 -250 c 5
- 273 -350 l 5
- 228 -350 l 5
- 228 -249 l 5
- 100 -238 0 -131 0 0 c 4
- 0 132 99 238 228 249 c 5
- 228 350 l 5
-228 -204 m 5
- 228 204 l 5
- 125 193 45 106 45 0 c 4
- 45 -106 125 -193 228 -204 c 5
-273 -204 m 5
- 375 -193 455 -105 455 0 c 4
- 455 105 375 193 273 204 c 5
- 273 -204 l 5
-EndSplineSet
-Validated: 1
-EndChar
-
-StartChar: uniE914
-Encoding: 129 59668 129
-Width: 500
-Flags: HMW
-LayerCount: 2
-Fore
-SplineSet
-463 130 m 0
- 464 127 465 123 465 120 c 0
- 465 112 461 103 454 99 c 0
- 451 97 447 96 443 96 c 0
- 435 96 427 100 420 110 c 1
- 386 173 323 205 250 205 c 8
- 137 205 45 113 45 0 c 24
- 45 -113 137 -205 250 -205 c 16
- 330 -205 386 -174 420 -110 c 0
- 424 -102 434 -97 444 -97 c 0
- 448 -97 451 -97 454 -99 c 0
- 462 -104 466 -111 466 -118 c 0
- 466 -122 465 -126 463 -130 c 0
- 420 -211 350 -250 250 -250 c 8
- 112 -250 0 -138 0 0 c 24
- 0 139 111 250 250 250 c 0
- 344 250 420 210 463 130 c 0
-250 -60 m 3
- 217 -60 190 -33 190 0 c 3
- 190 33 217 60 250 60 c 3
- 283 60 310 33 310 0 c 3
- 310 -33 283 -60 250 -60 c 3
-EndSplineSet
-Validated: 1
-EndChar
-
StartChar: uniE915
-Encoding: 130 59669 130
+Encoding: 127 59669 127
Width: 500
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4452,65 +4332,28 @@
344 250 420 210 463 130 c 0
EndSplineSet
Validated: 1
EndChar
-StartChar: uniE918
-Encoding: 131 59672 131
-Width: 500
-Flags: HMW
-LayerCount: 2
-Fore
-SplineSet
-273 249 m 1
- 356 243 424 203 463 130 c 0
- 464 127 465 123 465 120 c 0
- 465 112 461 103 454 99 c 0
- 451 97 447 96 443 96 c 0
- 435 96 427 100 420 110 c 0
- 390 166 336 198 273 204 c 1
- 273 -204 l 1
- 341 -199 389 -168 420 -110 c 0
- 424 -102 434 -97 444 -97 c 0
- 448 -97 451 -97 454 -99 c 0
- 462 -104 466 -111 466 -118 c 0
- 466 -122 465 -126 463 -130 c 0
- 423 -205 361 -244 273 -249 c 1
- 273 -350 l 1
- 228 -350 l 1
- 228 -249 l 1
- 100 -238 0 -131 0 0 c 0
- 0 132 99 238 228 249 c 1
- 228 350 l 1
- 273 350 l 1
- 273 249 l 1
-228 -204 m 1
- 228 204 l 1
- 125 193 45 106 45 0 c 0
- 45 -106 125 -193 228 -204 c 1
-EndSplineSet
-Validated: 1
-EndChar
-
StartChar: uniE920
-Encoding: 132 59680 132
+Encoding: 128 59680 128
Width: 120
Flags: HMW
LayerCount: 2
Fore
SplineSet
-60 -60 m 3
- 27 -60 0 -33 0 0 c 3
- 0 33 27 60 60 60 c 3
- 93 60 120 33 120 0 c 3
- 120 -33 93 -60 60 -60 c 3
+60 -60 m 7
+ 27 -60 0 -33 0 0 c 7
+ 0 33 27 60 60 60 c 7
+ 93 60 120 33 120 0 c 7
+ 120 -33 93 -60 60 -60 c 7
EndSplineSet
Validated: 1
EndChar
StartChar: uniE925
-Encoding: 133 59685 133
+Encoding: 129 59685 129
Width: 45
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4522,11 +4365,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniE95D
-Encoding: 134 59741 134
+Encoding: 130 59741 130
Width: 540
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4551,11 +4394,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniEA02
-Encoding: 135 59906 135
+Encoding: 131 59906 131
Width: 250
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4575,11 +4418,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniEAA4
-Encoding: 136 60068 136
+Encoding: 132 60068 132
Width: 250
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4599,11 +4442,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniECA2
-Encoding: 137 60578 137
+Encoding: 133 60578 133
Width: 450
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4620,11 +4463,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniECA3
-Encoding: 138 60579 138
+Encoding: 134 60579 134
Width: 328
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4647,11 +4490,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniECA5
-Encoding: 139 60581 139
+Encoding: 135 60581 135
Width: 320
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4666,11 +4509,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniECA7
-Encoding: 140 60583 140
+Encoding: 136 60583 136
Width: 522
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4690,11 +4533,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniECA9
-Encoding: 141 60585 141
+Encoding: 137 60585 137
Width: 522
Flags: HMW
LayerCount: 2
Fore
SplineSet
@@ -4720,11 +4563,11 @@
EndSplineSet
Validated: 1
EndChar
StartChar: uniECB7
-Encoding: 142 60599 142
+Encoding: 138 60599 138
Width: 100
Flags: HMW
LayerCount: 2
Fore
SplineSet
Index: modules/MIDI.js
==================================================================
--- modules/MIDI.js
+++ modules/MIDI.js
@@ -1,8 +1,8 @@
// MIDI.js - module to handle the %%MIDI parameters
//
-// Copyright (C) 2019-2023 Jean-Francois Moine
+// Copyright (C) 2019-2024 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
@@ -17,19 +17,20 @@
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg. If not, see .
//
// This module is loaded when "%%MIDI" appears in a ABC source.
//
-// Parameters (see abcMIDI for details)
+// Parameters (see abcMIDI - https://abcmidi.sourceforge.io/ - for details)
// %%MIDI channel n
// %%MIDI program [channel] n
// %%MIDI control k v
// %%MIDI drummap ABC_note MIDI_pitch
// %%MIDI temperamentequal nedo
// %%MIDI chordname
// %%MIDI chordprog <#MIDI program> [octave=]
// %%MIDI chordvol
+// %%MIDI gchord
// %%MIDI gchordon
// %%MIDI gchordoff
// Using %%MIDI drummap creates a voicemap named "MIDIdrum".
// This name must be used if some print map is required:
@@ -119,32 +120,40 @@
// break
// case "droneon": // %%MIDI droneon
// break
// case "droneoff": // %%MIDI droneoff
// break
-// case "gchord": // %%MIDI gchord
+ case "gchord": // %%MIDI gchord
// // z rest
// // c chord
// // f fundamental
// // b fundamental + chord
+// // G/H/I/J/K individual notes starting
+// // from the lowest note of the chord
+// // g/h/i/j/k an octave above these
// // defaults:
// // M:2/4 or 4/4 fzczfzcz
// // M:3/4 fzczcz
// // M:6/8 fzcfzc
// // M:9/8 fzcfzcfzc
-// break
+ // fall thru
case "gchordon": // %%MIDI gchordon
case "gchordoff": // %%MIDI gchordoff
if (!cfmt.chord)
cfmt.chord = {}
if (abc.parse.state >= 2
&& curvoice) {
s = abc.new_block("midigch")
s.play = s.invis = 1 //true
- s.on = a[1][7] == 'n'
- } else {
+ if (a[1][6] == 'o')
+ s.on = a[1][7] == 'n'
+ else
+ s.rhy = a[2] // chord rhythm
+ } else if (a[1][6] == 'o') {
cfmt.chord.gchon = a[1][7] == 'n'
+ } else {
+ cfmt.chord.rhy = a[2]
}
break
case "channel":
v = parseInt(a[2])
if (isNaN(v) || v <= 0 || v > 16) {
@@ -153,11 +162,11 @@
}
v-- // channel range 1..16 => 0..15
if (abc.parse.state == 3) {
s = abc.new_block("midiprog")
s.play = s.invis = 1 //true
- curvoice.chn = s.chn = v
+ s.chn = v
} else {
abc.set_v_param("channel", v)
}
break
case "drummap":
@@ -178,28 +187,34 @@
}
this.do_pscom("map MIDIdrum " + a[2] + " play=" + n)
abc.set_v_param("mididrum", "MIDIdrum")
break
case "program":
- if (a[3] != undefined) { // with a channel
- abc2svg.MIDI.do_midi.call(abc, "MIDI channel " + a[2])
- v = a[3]
+ a.shift()
+ v = []
+ if (a[2]) {
+ v[0] = +a[2] // program
+ v[1] = +a[1] // channel
} else {
- v = a[2];
+ v[0] = +a[1]
+ v[1] = 0
}
- v = parseInt(v)
- if (isNaN(v) || v < 0 || v > 127) {
+ if (isNaN(v[0]) || v[0] < 0 || v[0] > 127
+ || (v[1]
+ && (isNaN(v[1]) || v[1] <= 0 || v[1] > 16))) {
abc.syntax(1, abc.errs.bad_val, "%%MIDI program")
break
}
if (abc.parse.state == 3) {
s = abc.new_block("midiprog");
s.play = s.invis = 1 //true
- s.instr = v
- s.chn = curvoice.chn
+ s.instr = v[0]
+ s.chn = v[1] > 0
+ ? (v[1] - 1)
+ : curvoice.v < 9 ? curvoice.v : curvoice.v + 1
} else {
- abc.set_v_param("instr", v)
+ abc.set_v_param("instr", a.slice(1).join(' '))
}
break
case "control":
n = parseInt(a[2])
if (isNaN(n) || n < 0 || n > 127) {
@@ -304,22 +319,23 @@
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
- s.chn = curvoice.chn = a[++i]
+ s.chn = +a[++i]
break
case "instr=": // %%MIDI program
s = abc.new_block("midiprog")
s.play = s.invis = 1 //true
- s.instr = a[++i]
- if (curvoice.chn == undefined) {
- curvoice.chn = curvoice.v < 9 ?
+ s.instr = a[++i].split(' ')
+ if (s.instr[1])
+ s.chn = s.instr.shift() - 1
+ else
+ s.chn = curvoice.v < 9 ?
curvoice.v :
curvoice.v + 1
- }
- s.chn = curvoice.chn
+ s.instr = +s.instr[0]
break
case "midictl=": // %%MIDI control
if (!curvoice.midictl)
curvoice.midictl = []
item = a[++i].split(' ');
Index: modules/break.js
==================================================================
--- modules/break.js
+++ modules/break.js
@@ -1,8 +1,8 @@
// break.js - module to handle the %%break command
//
-// Copyright (C) 2018-2023 Jean-Francois Moine
+// Copyright (C) 2018-2024 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
@@ -28,11 +28,11 @@
abc2svg.break = {
// get the %%break parameters
get_break: function(parm) {
var C = abc2svg.C,
- b, c, d, sq,
+ b, c, d, n, sq,
a = parm.split(/[ ,]/),
cfmt = this.cfmt()
if (!cfmt.break)
cfmt.break = []
@@ -64,12 +64,12 @@
}
}, // get_break()
// insert the EOLs of %%break
do_break: function() {
- var i, m, t, brk, seq,
- voice_tb = this.get_voice_tb()
+ var i, m, t, brk, s, seq,
+ voice_tb = this.get_voice_tb(),
v = this.get_cur_sy().top_voice,
s1 = voice_tb[v].sym,
cfmt = this.cfmt()
for (i = 0; i < cfmt.break.length; i++) {
Index: modules/chordnames.js
==================================================================
--- modules/chordnames.js
+++ modules/chordnames.js
@@ -20,11 +20,11 @@
// This module is loaded by %%chordnames.
//
// Parameters
// 1st syntax:
// %%chordnames
-// Each name replace one chord. The order is:
+// Each name replaces one chord. The order is:
// CDEFGAB
// 2nd syntax:
// %%chordnames
// The key may be a chord letter ('A') with/or an accidental
// Example:
Index: modules/clip.js
==================================================================
--- modules/clip.js
+++ modules/clip.js
@@ -1,8 +1,8 @@
// clip.js - module to handle the %%clip command
//
-// Copyright (C) 2018-2023 Jean-Francois Moine
+// Copyright (C) 2018-2024 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
@@ -66,13 +66,41 @@
this.cfmt().clip = [b, c]
}, // get_clip()
// cut the tune
do_clip: function() {
- var C = abc2svg.C
+ var C = abc2svg.C,
+ abc = this,
voice_tb = this.get_voice_tb(),
cfmt = this.cfmt()
+
+ // check the pointers (ties and slurs)
+ function chkptr(last) {
+ var i, sl, s2, m, nt,
+ tim = last.time,
+ s = abc.get_tsfirst()
+
+ do {
+ if (s.sls) {
+ for (i = 0; i < s.sls.length; i++) {
+ sl = s.sls[i]
+ s2 = sl.se // slur end
+ if (s2.time >= tim)
+ sl.loc = 'o' // no end
+ }
+ }
+ if (s.ti1 // if tie
+ && s.time + s.dur >= tim) { // ending out of the clip seq.
+ for (m = 0; m <= s.nhd; m++) {
+ nt = s.notes[m]
+ if (nt.tie_e && nt.tie_e.s.time >= tim)
+ delete nt.tie_e // no end
+ }
+ }
+ s = s.ts_next
+ } while (s)
+ } // chkptr()
// go to a global (measure + time)
function go_global_time(s, sel) {
var s2, bar_time, seq
@@ -81,11 +109,11 @@
for (s2 = s; s2; s2 = s2.ts_next) {
if (s2.type == C.BAR
&& s2.time != 0)
break
}
- if (s2.time < voice_tb[this.get_cur_sy().top_voice].
+ if (s2.time < voice_tb[abc.get_cur_sy().top_voice].
meter.wmeasure)
s = s2
}
} else {
for ( ; s; s = s.ts_next) {
@@ -143,10 +171,11 @@
case C.CLEF:
s2.p_v.clef = s2
break
case C.KEY:
s2.p_v.key = this.clone(s2.as.u.key)
+ s2.p_v.ckey = this.clone(s2.as.u.ckey)
break
case C.METER:
s2.p_v.meter = this.clone(s2.as.u.meter)
break
case C.STAVES:
@@ -163,12 +192,23 @@
break
}
}
p_voice.sym = s2
}
- this.set_tsfirst(s)
- delete s.ts_prev
+ s2 = this.get_tsfirst()
+ if (s != s2) {
+ if (s2.type == C.STAVES) {
+ s2.ts_next = s
+ s.ts_prev = s2
+ s2.next = s2.p_v.sym
+ s2.p_v.sym = s2
+ s2.next.prev = s2
+ } else {
+ this.set_tsfirst(s)
+ delete s.ts_prev
+ }
+ }
}
/* remove the end of the tune */
s = go_global_time(s, cfmt.clip[1])
if (!s)
@@ -192,10 +232,12 @@
}
if (!s2)
p_voice.sym = null
}
delete s.ts_prev.ts_next
+
+ chkptr(s.ts_prev) // check the pointers (ties and slurs)
}, // do_clip()
do_pscom: function (of, text) {
if (text.slice(0, 5) == "clip ")
abc2svg.clip.get_clip.call(this, text)
Index: modules/combine.js
==================================================================
--- modules/combine.js
+++ modules/combine.js
@@ -75,12 +75,10 @@
} else if (s.combine <= 2) {
return //false
}
return true
}
- if (s2.a_ly)
- return false
if (s2.beam_st != s.beam_st
|| s2.beam_end != s.beam_end)
return false;
nhd2 = s2.nhd
if (s.combine <= 1
Index: modules/gamelan.js
==================================================================
--- modules/gamelan.js
+++ modules/gamelan.js
@@ -1,8 +1,8 @@
// gamelan.js - module to output Gamelan (indonesian) music sheets
//
-// Copyright (C) 2020-2023 Jean-Francois Moine
+// Copyright (C) 2020-2024 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
@@ -62,11 +62,12 @@
of(p)
},
// adjust some symbols before the generation
output_music: function(of) {
- var C = abc2svg.C,
+ var v,
+ C = abc2svg.C,
abc = this,
cur_sy = abc.get_cur_sy(),
voice_tb = abc.get_voice_tb()
if (!abc.cfmt().gamelan) {
Index: modules/grid2.js
==================================================================
--- modules/grid2.js
+++ modules/grid2.js
@@ -1,8 +1,8 @@
// grid2.js - module to replace a voice in the music by a chord grid
//
-// Copyright (C) 2018-2023 Jean-Francois Moine
+// Copyright (C) 2018-2024 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
@@ -40,11 +40,11 @@
for (v = 0; v < voice_tb.length; v++) {
p_v = voice_tb[v]
if (!p_v.grid2)
continue
- curvoice = p_v
+ this.set_curvoice(p_v)
this.set_v_param("stafflines", "...") // no staff
p_v.clef.invis = true; // no clef
p_v.key.k_sf = 0 // no key signature
delete p_v.key.k_a_acc
p_v.staffnonote = 2 // draw the staff
Index: modules/grid3.js
==================================================================
--- modules/grid3.js
+++ modules/grid3.js
@@ -1,8 +1,8 @@
// grid3.js - module to insert a manual chords
//
-// Copyright (C) 2020-2023 Jean-Francois Moine
+// Copyright (C) 2020-2024 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
@@ -18,20 +18,22 @@
// along with abc2svg. If not, see .
//
// This module is loaded when "%%begingrid" appears in a ABC source.
//
// Parameters
-// %%begingrid [ chord-define [ noprint ] ]
+// %%begingrid [ chord-define [ noprint ] ] [ lm= ] [ width= ]
// list of chords, '-' or '.', measure bars ('|') and ':' for repeat
// %%endgrid
//
// %%gridfont font_name size (default: 'serif 16')
//
// When this command appears inside a tune and when 'chord-define' is present,
// the chords are used to define the chord symbols that are displayed
// above the staff system.
// When 'noprint' is also present, the grid itself is not displayed.
+//
+// The left margin may be '-1' to center the grid (default).
if (typeof abc2svg == "undefined")
var abc2svg = {}
abc2svg.grid3 = {
@@ -120,16 +122,25 @@
// 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
+ if (s.width) {
+ w = s.width
+ wc = w / nc
+ } else {
+ w = wc * nc // grid width
+ }
+ x0 = s.lm < 0
+ ? (img.width / cfmt.scale - w) / 2 // center
+ : s.lm + 1
+ if ((s.fmt || cfmt).trimsvg) // adjust the SVG width
+ img.wx = x0 + w - img.lm - img.rm
// generate the cells
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
@@ -267,11 +278,13 @@
abc.blk_flush()
}, // block_gen()
// handle %%begingrid
do_begin_end: function(of, type, opt, txt) {
- var vt = this.get_voice_tb()
+ var i, s, v,
+ lm = -1, // left margin - default: center the grid
+ width // grid width - default: computed
if (type != "grid") {
of(type, opt, txt)
return
}
@@ -285,28 +298,58 @@
// case 'b': return "\u266d"
}
return "\u266d"
})
- if (opt.indexOf("chord-define") >= 0)
- this.cfmt().csdef = txt
- if (opt.indexOf("noprint") < 0) {
+ opt = opt.trim().split(/\s+/)
+ while (1) {
+ i = opt.shift()
+ if (!i)
+ break
+ switch (i[0]) {
+ case 'c': // chord-define
+ this.cfmt().csdef = txt
+ break
+ case 'n': // noprint
+ type = ""
+ break
+ case 'l': // lm=..
+ case 'w': // width=..
+ v = i.match(/-?[\d.]+.?.?/)
+ if (v) {
+ v = this.get_unit(v[0])
+ if (!isNaN(v)) {
+ if (i[0] == 'l')
+ lm = v
+ else
+ width = v
+ }
+ }
+ break
+ }
+ }
+
+ if (type) {
type += "3"
if (this.parse.state >= 2) {
s = this.new_block(type)
s.text = txt
+ s.lm = lm
+ s.width = width
} else {
abc2svg.grid3.block_gen.call(this, null, {
subtype: type,
- text: txt
+ text: txt,
+ lm: lm,
+ width: width
})
}
}
}, // do_begin_end()
output_music: function(of) {
- var ln, i, dt, ss, ntim, p_vc, s3,
+ var ln, i, dt, ss, ntim, p_vc, s3, cl,
C = abc2svg.C,
abc = this,
s = abc.get_tsfirst(),
vt = abc.get_voice_tb(),
t = abc.cfmt().csdef,
Index: modules/jianpu.js
==================================================================
--- modules/jianpu.js
+++ modules/jianpu.js
@@ -356,11 +356,11 @@
of()
}, // output_music()
draw_symbols: function(of, p_voice) {
- var s, s2, nl, y,
+ var s, s2, g, nl, y,
C = abc2svg.C,
abc = this,
dot = "\ue1e7",
anno_a = abc.anno_a,
staff_tb = abc.get_staff_tb(),
@@ -373,15 +373,14 @@
return
}
// draw the duration lines under the notes
function draw_dur(s1, x, y, s2, n, nl) {
- var s, s3,
- sc = s1.grace ? .5 : 1
+ var s, s3
xypath(x - 3, y + 5)
- out_svg('h' + ((s2.x - s1.x) / sc + 8).toFixed(1) + '"/>\n') // "
+ out_svg('h' + (s2.x - s1.x + 8).toFixed(1) + '"/>\n')
y -= 2.5
while (++n <= nl) {
s = s1
while (1) {
if (s.nflags && s.nflags >= n) {
@@ -400,10 +399,51 @@
break
s = s.next
}
}
} // draw_dur()
+
+ // draw the duration lines of grace notes
+ // and the slurs
+ // (assume all notes have the same duration)
+ function draw_dgr(s) {
+ var g, x, l,
+ y = staff_tb[s.st].y,
+ s2 = s.extra,
+ nl = s2.nflags
+
+ for (g = s2; g.next; g = g.next)
+ ;
+ out_svg('\n')
+ abc.stv_g().g++ // in container
+ l = ((g.x - s2.x) * 2 + 8).toFixed(1)
+ y = (nl > 0 ? 5 * nl : 0) + 2
+ if (nl > 0) {
+ xypath(-3, 0)
+ while (--nl >= 0) {
+ out_svg('m0 4h' + l)
+ l = -l
+ }
+ out_svg('"/>\n')
+ }
+ if (s.fmt.graceslurs // if a slur,
+ && !s.sl1) { // but not an explicit one
+ l = Math.abs(l)
+ out_svg('\n')
+ }
+ out_svg('\n')
+ abc.stv_g().g--
+ } // draw_dgr()
function out_mus(x, y, p) {
out_svg('' + p + '\n')
@@ -502,20 +542,25 @@
nl = s.nflags
if (nl <= 0)
continue
y = staff_tb[s.st].y
s2 = s
- while (s.next && s.next.nflags > 0) {
+ while (s.next) {
s = s.next
if (s.nflags > nl)
nl = s.nflags
if (s.beam_end)
break
+ if (s.type == C.GRACE)
+ draw_dgr(s)
}
if (s.dy)
y += s.dy
draw_dur(s2, s2.x, y, s, 1, nl)
+ break
+ case C.GRACE:
+ draw_dgr(s)
break
}
}
}, // draw_symbols()
Index: modules/page.js
==================================================================
--- modules/page.js
+++ modules/page.js
@@ -249,11 +249,11 @@
}, // gen_hf()
// start a new page
open_page: function(page,
ht) { // spacing under the header
- var h, l,
+ var h,
abc = page.abc,
cfmt = abc.cfmt(),
sty = '
\n'
} else {
sty = ''
@@ -297,13 +299,15 @@
} else {
abc2svg.page.svg_tag(cfmt.pagewidth, ht, "header")
abc2svg.page.user_out('\n')
}
if (page.footer) {
- l = abc.get_font_style().length
+ abc.clr_sty()
+ if (!cfmt.footerfont)
+ abc.param_set_font("footerfont", "text,serif 16")
page.fh = abc2svg.page.gen_hf(page, "footer")
- sty = abc.get_font_style().slice(l) // new style(s)
+ sty = abc.get_font_style() // new style(s)
if (cfmt.fullsvg || sty != page.fsty) {
page.fsty = sty
page.ffsty = '\n'
} else {
page.ffsty = ''
@@ -431,10 +435,11 @@
botmargin: 38, // 1cm
// gutter: 0,
h: 0, // current page height
pn: 0, // page number
pna: 0, // absolute page number
+ ffsty: '', // style of the footer
first: true // no skip to next page
}
// don't let the backend handle the header/footer
if (cfmt.header) {
Index: modules/strftime.js
==================================================================
--- modules/strftime.js
+++ modules/strftime.js
@@ -1,7 +1,7 @@
-/* Port of strftime() by T. H. Doan (https://thdoan.github.io/strftime/)
- *
+// Port of strftime() by T. H. Doan (https://thdoan.github.io/strftime/)
+/*
* Day of year (%j) code based on Joe Orost's answer:
* http://stackoverflow.com/questions/8619879/javascript-calculate-the-day-of-the-year-1-366
*
* Week number (%V) code based on Taco van den Broek's prototype:
* http://techblog.procurios.nl/k/news/view/33796/14863/calculate-iso-8601-week-and-year-in-javascript.html
Index: modules/strtab.js
==================================================================
--- modules/strtab.js
+++ modules/strtab.js
@@ -1,8 +1,8 @@
// abc2svg - strtab.js - tablature for string instruments
//
-// Copyright (C) 2020-2023 Jean-Francois Moine
+// Copyright (C) 2020-2024 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
@@ -50,14 +50,14 @@
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)
+ if (!not.nb)
continue
x = s.x - 3
- if (not.nb >= 10)
+ if (not.nb.length > 1)
x -= 3
y = 3 * (not.pit - 18)
abc.out_svg(' i) {
- lstr[st][0] = i
- lstr[st][1] = s
- }
- s.stemless = true
+ if (p_v.pos.stm != C.SL_HIDDEN) {
+ if (!lstr[st])
+ lstr[st] = [ 10, null, C.BLEN ]
+ if (lstr[st][0] > i) {
+ lstr[st][0] = i // lowest string
+ lstr[st][1] = s
+ }
+ if (s.dur < lstr[st][2])
+ lstr[st][2] = s.dur
+ }
+ s.stemless = 1 //true
+ if (s.dots) { // have nicer dots
+ s.xmx = 0
+ for (m = 0; m <= s.nhd; m++)
+ s.notes[m].shhd = 0
+ s.dot_low = 0
}
} // set_pit()
function set_notes(p_v, s) {
var i, bi, bn, nt, m, n, ns
@@ -333,12 +343,14 @@
for (i = 0; i < nt.sls.length; i++) {
nt.sls[i].ty &= ~0x07
nt.sls[i].ty |= C.SL_ABOVE
}
}
- if (nt.nb != undefined)
+ if (nt.nb) {
+ delete nt.a_dd
continue
+ }
if (nt.a_dd) {
i = nt.a_dd.length
while (--i >= 0) {
bi = strnum(nt.a_dd[i].name)
if (bi >= 0) {
@@ -370,11 +382,13 @@
}
set_pit(p_v, s, nt, bi)
}
s.y = 3 * (nt.pit - 18)
- s.ymn = 0 // don't get space below the tablature
+
+ // if no stem, don't get space below the tablature
+ s.ymn = s.stemless ? -12 : 0
} // set_notes()
// get the string number from the decoration
// format is either !s! or !!
function strnum(n) {
@@ -392,22 +406,21 @@
for (i = 0; i < p_v[n].tab.length; i++)
p_v[n].tab[i] += m
}
}
- of() // do the normal work
+ of(width) // 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]) {
+ if (lstr[i] && lstr[i][2] < C.BLEN)
lstr[i][1].tabst = 1
- lstr[i] = null
- }
+ lstr[i] = null
}
}
p_v = s.p_v
if (!p_v.tab)
@@ -425,21 +438,23 @@
break
case C.GRACE:
if (p_v.pos.gst == C.SL_HIDDEN)
s.sappo = 0
for (g = s.extra; g; g = g.next) {
- if (p_v.pos.gst == C.SL_HIDDEN)
- g.stemless = true
set_notes(p_v, g)
}
break
case C.NOTE:
set_notes(p_v, s)
break
}
}
- }, // set_stems()
+ for (i = 0; i < lstr.length; i++) {
+ if (lstr[i] && lstr[i][2] < C.BLEN)
+ lstr[i][1].tabst = 1 // top of stem
+ }
+ }, // set_glue()
// get the parameters of the current voice
set_vp: function(of, a) {
var i, e, g, tab, strs, ok,
p_v = this.get_curvoice()
@@ -617,11 +632,11 @@
this.syntax(1, "Bad strings in tablature")
ok = false
}
} else if (!p_v.tab) {
tab = p_v.diafret ?
- [10, 14, 17] : // dulcimer
+ [17, 14, 10] : // dulcimer
[40, 45, 50, 55, 59, 64] // guitar strings
} else {
tab = p_v.tab
}
}
@@ -676,26 +691,24 @@
set_hooks: function(abc) {
abc.draw_symbols = abc2svg.strtab.draw_symbols.bind(abc, abc.draw_symbols)
abc.gch_build = abc2svg.strtab.csan_bld.bind(abc, abc.gch_build)
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_sym_glue = abc2svg.strtab.set_glue.bind(abc, abc.set_sym_glue)
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() {}
+ decos["1s"] = "0 nil 0 0 0"
+ decos["2s"] = "0 nil 0 0 0"
+ decos["3s"] = "0 nil 0 0 0"
+ decos["4s"] = "0 nil 0 0 0"
+ decos["5s"] = "0 nil 0 0 0"
+ decos["6s"] = "0 nil 0 0 0"
abc.add_style("\n.bn{font:bold 7px sans-serif}")
} // set_hooks()
} // strtab
if (!abc2svg.mhooks)
abc2svg.mhooks = {}
abc2svg.mhooks.strtab = abc2svg.strtab.set_hooks
ADDED modules/tempo.js
Index: modules/tempo.js
==================================================================
--- /dev/null
+++ modules/tempo.js
@@ -0,0 +1,52 @@
+// abc2svg - temponame.js - change/create the beats per minute of a tempo
+//
+// Copyright (C) 2024 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 .
+//
+// This module is loaded by %%temponame.
+//
+// Parameters
+// %%temponame
+// The names of the tempos are in the table abc2svg.tmp_tb (cf util/sndgen.js)
+
+if (typeof abc2svg == "undefined")
+ var abc2svg = {}
+
+abc2svg.temponame = {
+
+ set_fmt: function(of, cmd, parm) {
+ var r
+
+ if (cmd == "temponame") {
+ r = /("[^"]+"|\w+)\s+(\d+)/.exec(parm) // "
+ if (r)
+ abc2svg.tmp_tb[r[1]] = r[2]
+// else
+// error
+ return
+ }
+ of(cmd, parm)
+ }, // set_fmt()
+
+ set_hooks: function(abc) {
+ abc.set_format = abc2svg.temponame.set_fmt.bind(abc, abc.set_format)
+ } // set_hooks()
+} // temponame
+
+if (!abc2svg.mhooks)
+ abc2svg.mhooks = {}
+abc2svg.mhooks.temponame = abc2svg.temponame.set_hooks
Index: modules/tropt.js
==================================================================
--- modules/tropt.js
+++ modules/tropt.js
@@ -113,10 +113,12 @@
continue
}
if (ok(s, np)) {
nt.pit = np
nt.acc = na
+ if (p_v.map)
+ this.set_map(p_v, nt, na)
}
}
}
}
of(last_s)
DELETED package.json
Index: package.json
==================================================================
--- package.json
+++ /dev/null
@@ -1,107 +0,0 @@
-{
- "name": "abc2svg",
- "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",
- "chord-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",
- "grid-1.js",
- "grid2-1.js",
- "grid3-1.js",
- "json-1.js",
- "jazzchord-1.js",
- "jianpu-1.js",
- "mdnn-1.js",
- "nns-1.js",
- "page-1.js",
- "pedline-1.js",
- "perc-1.js",
- "play-1.js",
- "psvg-1.js",
- "roman-1.js",
- "snd-1.js",
- "soloffs-1.js",
- "sth-1.js",
- "strtab-1.js",
- "temper-1.js",
- "toabc.js",
- "toaudio-1.js",
- "toabw.js",
- "tohtml.js",
- "tomei.js",
- "tonotes.js",
- "toodt.js",
- "toparam.js",
- "toxhtml.js"
- ],
- "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": [
- "ABC",
- "music",
- "SVG"
- ],
- "author": "Jean-François Moine",
- "license": "LGPL-3.0",
- "bugs": {
- "url": "https://chiselapp.com/user/moinejf/repository/abc2svg/reportlist"
- },
- "homepage": "https://chiselapp.com/user/moinejf/repository/abc2svg"
-}
Index: toabc.js
==================================================================
--- toabc.js
+++ toabc.js
@@ -1,8 +1,8 @@
// abc2svg - toabc.js - convert ABC to ABC
//
-// Copyright (C) 2016-2023 Jean-Francois Moine
+// Copyright (C) 2016-2024 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
@@ -457,11 +457,11 @@
if (ly.ln) {
w[i] += t
if (ly.ln == 1)
w[i] += '-'
} else {
- t = t.replace(/ /g, '~')
+ t = t.replace(/ /g, '~')
t = t.replace(/-/g, '\\-')
if (t.slice(-1) != "-")
t += ' '
w[i] += t
}
@@ -986,14 +986,14 @@
line += 'x' + dur_dump(s.time - vti[v])
}
vti[v] = s.time
}
sym_dump(s)
- if (s.dur)
+ if (s.dur) {
vti[v] = s.time + s.dur
- if (s.next) {
- if (s.beam_end && !s.beam_st && !s.next.beam_end)
+ if (s.dur < C.BLEN / 4 && s.beam_end
+ && s.next && s.next.dur && s.next.dur < C.BLEN / 4)
line += ' '
}
if (line)
vo[v] += line
}
ADDED tosvg.js
Index: tosvg.js
==================================================================
--- /dev/null
+++ tosvg.js
@@ -0,0 +1,100 @@
+// txtmus - tosvg.js - SVG generation
+//
+// Copyright (C) 2024 Jean-Francois Moine
+//
+// This file is part of txtmus.
+//
+// txtmus 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.
+//
+// txtmus 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 txtmus. If not, see .
+
+ var fn,
+ out = [], // output without SVG container
+ yo = 0, // offset of the next SVG
+ w = 0 // max width
+
+if (!tm)
+ var tm = abc2svg
+
+// fix the problem about the text multi-coordinates in librsvg
+function bug(p) {
+ var i, t, r, x, y, c, o,
+ j = 0
+
+ while (1) {
+ i = p.indexOf("]*>(.+)/)
+ // r[1] = x list, r[2] = y list, r[3] = characters
+
+ if (!r || r[1].indexOf(',') < 0)
+ continue
+ x = r[1].split(',')
+ y = r[2].split(',')
+ k = 0
+ o = '' + r[3][0]
+ while (++k < x.length)
+ o += '\n'
+ + r[3][k] + ''
+ p = p.replace(t, o)
+ }
+ // not reached
+} //bug()
+
+// entry point from cmdline
+tm.abc_init =
+tm.mus_init = function(args) {
+ user.img_out = function(p) {
+ var i, h
+
+ switch (p.slice(0, 4)) {
+ case "")
+ out.push("")
+ break
+ }
+ }
+
+ // get the main music source file name
+ for (var i = 0; i < args.length; i++) {
+ fn = args[i]
+ if (!fn || fn[0] != '-')
+ break
+ i++
+ }
+} // mus_init()
+
+tm.abc_end =
+tm.mus_end = function() {
+ tm.print('')
+ if (user.errtxt)
+ tm.printErr(user.errtxt)
+}
Index: util/abcweb.js
==================================================================
--- util/abcweb.js
+++ util/abcweb.js
@@ -1,8 +1,8 @@
// abcweb-1.js file to include in html pages
//
-// Copyright (C) 2014-2023 Jean-Francois Moine
+// Copyright (C) 2014-2024 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
@@ -89,11 +89,11 @@
document.head.appendChild(s)
} // loadjs()
// function called when abcweb-1.js is fully loaded
function dom_loaded() {
- var abc, src, outb, err,
+ var abc, src, outb, err, e,
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,
@@ -105,21 +105,21 @@
}
// check which
became visible
// if yes, generate it
function visible() {
- var mu, r,
+ var i, r,
wh = window.innerHeight || document.documentElement.clientHeight
while (1) {
- mu = abc2svg.alldiv[0]
- if (!mu)
+ i = abc2svg.alldiv[0]
+ if (i == undefined)
break
- r = mu.d.getBoundingClientRect()
+ r = abc2svg.music[i].d.getBoundingClientRect()
if (r.top > wh)
break
- musgen(mu)
+ musgen(i)
abc2svg.alldiv.shift()
}
if (abc2svg.alldiv.length) {
if (!abc2svg.onscroll) {
abc2svg.onscroll = visible
@@ -141,11 +141,11 @@
// from class=".."
c = e.getAttribute("class")
if (c) {
c = '.' + c // selector
for (i = 0; i < sh.length; i++) {
- r = sh[i].rules
+ r = sh[i].cssRules
for (j = 0; j < r.length; j++) {
if (r[j].selectorText == c)
break
}
if (j < r.length)
@@ -274,83 +274,78 @@
if (i >= sa.length)
break
c = get_p(s) // get custom properties
div = document.createElement('div')
- if (s.text.indexOf('\nX:') < 0) {
+ if (!abc2svg.music[0].t
+ && 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
})
+ div.addEventListener('click', abc2svg.playseq)
}
s.parentNode.replaceChild(div, s)
}
} // save_music()
// generate a music sequence
// @mu = object { t: music source, d:
}
- function musgen(mu) {
- var t = mu.t
+ function musgen(ix) {
+ var mu = abc2svg.music[ix],
+ t = mu.t
if (busy) {
mu.w = 1 //true
return
}
busy = 1 //true
// render a music sequence
function render() {
- var i, j, e
+ var i, j, e, o,
+ f = abc.cfmt().with_source
// start the generation
outb = err = ""
abc.tosvg(mu.n, t) // music source
- abc2svg.abc_end() // close the page if %%pageheight
-
// mu.d can be null when parameters in query string
// and no ABC script with global parameters
if (mu.d) {
if (err)
outb += '
'
+ err + "
\n"
- if (abc.cfmt().with_source && outb)
- outb = '
\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
- }
+ abc2svg.abc_end(ix) // end of page (for %%pageheight, abcweb)
} // if (mu.d)
// 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])
+ musgen(i)
break
}
}
} // render()
@@ -393,10 +388,13 @@
include()
}
r.send()
} // include()
+ // musgen entry
+ abc = abc2svg.abc // reload the Abc instance after edition
+
// load the required modules and the include files before rendering
if (abc2svg.modules.load(t, include))
include()
} // musgen()
@@ -506,27 +504,38 @@
src = document.body.innerHTML
if (!abc2svg.Abc) {
abc2svg.loadjs("abc2svg-1.js", dom_loaded)
return
}
+
+ // create a hidden span for string width computation
+ e = document.createElement("span")
+ e.style.position = "absolute"
+ e.style.top =
+ e.style.padding = 0
+ e.style.visibility = "hidden"
+ e.style.lineHeight = 1
+ document.body.appendChild(e)
+ abc2svg.el = e
if (src.indexOf('type="text/vnd.abc"') < 0)
move_music(src)
save_music() // save the music sequences and replace them by
// initialize the generation
+ abc2svg.abc = // for external access
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)
- musgen(abc2svg.music[0]) // global definitions
+ musgen(0) // global definitions
// create a list of all
's
abc2svg.alldiv = []
for (var i = 1; i < abc2svg.music.length; i++)
- abc2svg.alldiv.push(abc2svg.music[i])
+ abc2svg.alldiv.push(i)
visible()
} // dom_loaded()
// ---- interface for changing the music source and redisplaying it ----
// get the music source of a
element
@@ -541,19 +550,42 @@
// return ""
} // get_music()
// set the music source of a
element and redisplay it
abc2svg.set_music = function(d, t) {
- var i, mu
+ var i, mu, tunes
+
+ // remove the tune data that will not be used anymore
+ function tune_purge(mu) {
+ var svg, cl, i,
+ e = mu.d.getElementsByTagName('svg')
+
+ for (i = 0; i < e.length; i++) { // look at the SVGs of the sequence
+ cl = e[i].getAttribute("class") // SVG class
+ if (cl)
+ cl = cl.match(/tune(\d+)/) // tune number
+ if (!cl)
+ continue
+ tunes[cl[1]] = null
+ }
+ } // tune_purge()
- for (var i = 1; i < abc2svg.music.length; i++) {
+ for (i = 1; i < abc2svg.music.length; i++) {
mu = abc2svg.music[i]
if (mu.d == d) {
mu.t = t
- abc2svg.musgen(mu)
+ tunes = abc2svg.abc.tunes
+ tune_purge(mu)
+ abc2svg.abc = new abc2svg.Abc(user) // use a new Abc instance
+
+ // copy the old tunes to the new instance
+ abc2svg.abc.tunes.push.apply(abc2svg.abc.tunes, tunes)
+ if (abc2svg.music[0].t)
+ abc2svg.musgen(0) // global definitions
+ abc2svg.musgen(i)
break
}
}
} // set_music()
// wait for the scripts to be loaded
window.addEventListener("load", dom_loaded, {once:true})
Index: util/abcweb1.js
==================================================================
--- util/abcweb1.js
+++ util/abcweb1.js
@@ -1,8 +1,8 @@
// abcweb1-1.js file to include in html pages with abc2svg-1.js
//
-// Copyright (C) 2019-2022 Jean-Francois Moine
+// Copyright (C) 2019-2024 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
@@ -58,11 +58,11 @@
if (typeof abc2svg == "undefined")
var abc2svg = {}
// function called when abc2svg is fully loaded
function dom_loaded() {
- var abc,
+ var abc, e,
new_page,
playing,
abcplay,
tune_dur, // scroll tune duration
@@ -314,10 +314,11 @@
// for being set later by follow() !
if (typeof follow == "function")
user.anno_stop = function(){}
tune_lst = []
+ abc2svg.abc = // for external access
abc = new abc2svg.Abc(user)
new_page = ""
// initialize the play follow function
if (typeof follow == "function")
@@ -512,10 +513,20 @@
}
playing = true
abcplay.play(s, null)
} // click()
+
+ // create a hidden span for string width computation
+ e = document.createElement("span")
+ e.style.position = "absolute"
+ e.style.top =
+ e.style.padding = 0
+ e.style.visibility = "hidden"
+ e.style.lineHeight = 1
+ document.body.appendChild(e)
+ abc2svg.el = e
// accept page formatting
abc2svg.abc_end = function() {}
// load the required modules, then render the music
Index: util/chord.js
==================================================================
--- util/chord.js
+++ util/chord.js
@@ -1,8 +1,8 @@
// chord.js - generation of accompaniment
//
-// Copyright (C) 2020-2022 Jean-Francois Moine and Seymour Shlien
+// Copyright (C) 2020-2024 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
@@ -16,132 +16,140 @@
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg. If not, see .
// -- chord table --
+// from https://en.wikipedia.org/wiki/Chord_(music)
// index = chord symbol type
-// value: array of strings
+// value: array of MIDI pitch / root
// index = inversion
-// string = list of 2 characters
-// 1st character = note (see abc2svg.letmid)
-// 2nd character = octave ('+', ' ', '-')
-abc2svg.ch_names = {
- '': ["C-E G C+", "E-C G C+", "G-C E G "],
- m: ["C-e G C+", "e-C G C+", "G-C e G "],
- '7': ["C-b-E G ", "E-C G b ", "G-E b C+", "b-E G C+"],
- m7: ["C-b-e G ", "e-C G b ", "G-e b C+", "b-e G C+"],
- m7b5: ["C-b-e g ", "e-C g b ", "g-e b C+", "b-e g C+"],
- M7: ["C-B-E G ", "E-C G B ", "G-E B C+", "B-E G C+"],
- '6': ["C-A-E G ", "E-C A B ", "A-E B C+", "B-E A C+"],
- m6: ["C-A-e G ", "e-C A B ", "A-e B C+", "B-e A C+"],
- aug: ["C-E a C+", "E-C a C+", "a-C E a "],
-// plus: ["C-E a C+", "E-C a C+", "a-C E a "],
- aug7: ["C-b-E a ", "E-C a b ", "a-E b C+", "b-E a C+"],
- dim: ["C-e g C+", "e-C g C+", "g-C e g "],
- dim7: ["C-e g A ", "e-C g A ", "g-e A C+", "A-C e G "],
- '9': ["C-b-E G D+", "E-C G b D+", "G-E b C+D+", "b-E G C+D+", "D-G-C E b "],
- m9: ["C-b-e G D+", "e-C G b D+", "G-e b C+D+", "b-e G C+D+", "D-G-C e b "],
- maj9: ["C-B-E G D+", "E-C G B D+", "G-E B C+D+", "B-E G C+D+", "D-G-C E B "],
- M9: ["C-B-E G D+", "E-C G B D+", "G-C E B D+", "B-E G C+D+", "D-G-C E B "],
- '11': ["C-b-E G D+F+", "E-C G b D+F+", "G-E b C+D+F+", "b-E G C+D+F+",
- "D-G-C E b F+", "F-D-G-C E b D+"],
- dim9: ["C-A-e g d+", "e-C g A d+", "g-C e A d+", "A-C e g d+", "D-g-C e A "],
- sus4: ["C-F G C+", "F-C G C+", "G-C F G "],
- sus9: ["C-D G C+", "D-C G C+", "G-C D G "],
- '7sus4': ["C-b-F G ", "F-C G b ", "G-F b C+", "b-C F G "],
- '7sus9': ["C-b-D G ", "D-C G b ", "G-D b C+", "b-C D G "],
- '5': ["C-G C+", "G-G C+"]
-} // ch_names
-
-abc2svg.midlet = "CdDeEFgGaAbB" // MIDI pitch -> letter
+abc2svg.chnm = {
+ '': [0, 4, 7],
+ '6': [0, 4, 7, 9],
+ '7': [0, 4, 7, 10],
+ M7: [0, 4, 7, 11],
+ aug: [0, 4, 8],
+ aug7: [0, 4, 8, 10],
+ m: [0, 3, 7],
+ m6: [0, 3, 7, 9],
+ m7: [0, 3, 7, 10],
+ mM7: [0, 3, 7, 11],
+ dim: [0, 3, 6],
+ dim7: [0, 3, 6, 9],
+ m7b5: [0, 3, 6, 10],
+ '9': [0, 4, 7, 10, 14],
+// m9:
+// maj9:
+// M9:
+ '11': [0, 4, 7, 10, 14, 17],
+ sus4: [0, 5, 7]
+// sus9:
+// '7sus4':
+// '7sus9':
+// '5':
+}
+
abc2svg.letmid = { // letter -> MIDI pitch
C: 0,
- d: 1,
D: 2,
- e: 3,
E: 4,
F: 5,
- g: 6,
G: 7,
- a: 8,
A: 9,
- b: 10,
B: 11
} // letmid
abc2svg.chord = function(first, // first symbol in time
voice_tb, // table of the voices
cfmt) { // tune parameters
- var chnm, i, k, vch, s, gchon,
+ var chnm, i, k, vch, s, gchon, s_ch, rhy, ti, dt,
+ md = first.p_v.meter.wmeasure, // measure duration
+ nextim = 0,
C = abc2svg.C,
trans = 48 + (cfmt.chord.trans ? cfmt.chord.trans * 12 : 0)
// create a chord according to the bass note
function chcr(b, ch) {
- var i, v,
- r = []
-
- b = abc2svg.midlet[b]
- i = ch.length
- while (--i > 0) {
- if (ch[i][0] == b) // search the bass in the chord
- break
- }
- ch = ch[i]
- for (i = 0; i < ch.length; i += 2) {
- v = abc2svg.letmid[ch[i]]
- switch (ch[i + 1]) {
- case '+': v += 12; break
- case '-': v -= 12; break
- }
- r.push(v)
- }
+ var t,
+ r = ch.slice(),
+ i = r.length
+
+ if (b) {
+ while (--i > 0) {
+ if (r[i] == b) // search the bass in the chord
+ break
+ }
+ if (i > 0)
+ r[0] = b, r[i] = 0
+ else
+ r.unshift(b)
+ }
+ r[0] -= 12 // bass one octave lower
return r
} // chcr()
- // get the playback part of the first chord symbol
- function filter(a_cs) {
- var i, cs, t
+ // return a new chord rhythm from a meter (M:)
+ function meterhy(s) {
+ var r,
+ t = s.a_meter[0].top,
+ b = s.a_meter[0].bot
+
+ switch (b) {
+ case '2':
+ b = 4
+ t *= 2
+ // fall thru
+ case undefined:
+ if (t[0] == 'C')
+ t = 4, b = 4
+ // fall thru
+ case '4':
+ if (t == '3')
+ return "fzczcz"
+ return "fzczfzczfzcz".slice(0, t * 2)
+ case '8':
+ return "fzcfzcfzcfzc".slice(0, t)
+ }
+ return 'z' // no chord
+ } // meterhy()
+
+ // build a chord rhythm
+ function bld_rhy(p) {
+ var i, c,
+ j = 0
- for (i = 0; i < a_cs.length; i++) {
- cs = a_cs[i]
- if (cs.type != 'g')
+ rhy = p[0]
+ for (i = 1; i < p.length; i++) {
+ c = p[i]
+ if (c >= '1' && c <= '9') {
+ while (--c > 0)
+ rhy += '+' // continue playing
continue
- t = cs.otext
- if (t.slice(-1) == ')') // if alternate chord
- t = t.replace(/\(.*/, '') // remove it
- return t.replace(/\(|\)|\[|\]/g,'') // remove ()[]
+ }
+ rhy += c
}
- } // filter()
+
+ dt = md / rhy.length // delta time
+ } // bld_rhy()
// generate a chord
- function gench(sb) {
- var r, ch, b, m, n, not,
- a = filter(sb.a_gch),
- s = {
- v: vch.v,
- p_v: vch,
- type: C.NOTE,
- time: sb.time,
- notes: []
- }
-
- if (!a)
- return
- a = a.match(/([A-GN])([#♯b♭]?)([^/]*)\/?(.*)/)
+ function gench(sb, i) {
+ var r, ch, b, m, n, nt,
+ a = sb.a_gch[i].otext
+
+ if (a.slice(-1) == ')') // if alternate chord
+ a = a.replace(/\(.*/, '') // remove it
+ a = a.replace(/\(|\)|\[|\]/g,'') // remove ()[]
+ a = a.match(/([A-G])([#♯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
- } else {
+ if (r == undefined) // "N" or no chord
+ return
+
switch (a[2]) {
case "#":
case "♯": r++; break
case "b":
case "♭": r--; break
@@ -164,68 +172,130 @@
case "#":
case "♯": b++; if (b >= 12) b = 0; break
case "b":
case "♭": b--; if (b < 0) b = 11; break
}
+ b = b - r
+ if (b < 0)
+ b += 12
}
}
- }
- 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
-
- // insert the chord in the chord voice and in the tune
- s.prev = vch.last_sym
- vch.last_sym.next = s
- s.ts_next = sb.ts_next
- sb.ts_next = s
- s.ts_prev = sb
- if (s.ts_next)
- s.ts_next.ts_prev = s
- vch.last_sym = s
+ nt = s_ch.notes[m]
+ if (!nt)
+ s_ch.notes[m] = nt = []
+ nt.midi = r + ch[m]
+ }
+ s_ch.nhd = n - 1
} // gench()
+
+ // stop the previous chord by setting its duration
+ function set_dur(s2, tim) { // previous chord
+ if (s2.dur
+ || tim == s2.time
+ || s2.nhd == undefined) // no chord yet
+ return
+ s2.dur = tim - s2.time
+ for (var m = 0; m <= s2.nhd; m++)
+ s2.notes[m].dur = s2.dur
+ } // set_dur()
+
+ // insert a chord in the chord voice
+ function insch(tim) {
+ if (s_ch.nhd == undefined)
+ return // no defined chord yet
+ var s, m,
+ s2 = vch.last_sym,
+ i = rhy[ti++]
+
+ switch (i) {
+ case '+': // same chord
+ if (rhy != '+')
+ return
+ i = 'b' // no rhythm
+ ti = 0
+ break
+ case undefined:
+ case 'z':
+ set_dur(s2, tim) // stop the previous chord
+ return
+ }
+
+ s = {
+ v: vch.v,
+ p_v: vch,
+ type: C.NOTE,
+ notes: []
+ }
+ s.nhd = s_ch.nhd
+// s.dur = s_ch.dur
+ for (m = 0; m <= s.nhd; m++)
+ s.notes[m] = {
+// dur: s_ch.notes[m].dur,
+ midi: s_ch.notes[m].midi
+ }
+ s.time = tim
+ switch (i) {
+ case 'c':
+ s.notes.shift() // remove the root note
+ s.nhd--
+ break
+ default:
+ i = "GHIJKghijk".indexOf(rhy[ti])
+ if (i < 0 // bad character
+ || i >= s.nhd) // out of chord
+ return
+ s.notes[0] = s.notes[i % 5]
+ if (i >= 5)
+ s.notes[0].midi += 12 // upper octave
+ // fall thru
+ case 'f':
+ s.nhd = 0 // keep the chord root
+// break
+ case 'b':
+ break
+ }
+ s.prev = s2 // previous chord
+ s2.next = s
+ set_dur(s2, tim) // stop the last chord
+
+ vch.last_sym = s
+ while (s2.time < tim
+ && s2.ts_next)
+ s2 = s2.ts_next
+ s.ts_prev = s2.ts_prev
+ s.ts_prev.ts_next = s
+ s.ts_next = s2
+ s2.ts_prev = s
+ } // insch()
// -- chord() --
// set the chordnames defined by %%MIDI chordname
+ chnm = abc2svg.chnm
if (cfmt.chord.names) {
- chnm = Object.create(abc2svg.ch_names)
for (k in cfmt.chord.names) {
- vch = ""
- for (i = 0; i < cfmt.chord.names[k].length; i++) {
- s = cfmt.chord.names[k][i]
- vch += abc2svg.midlet[s % 12]
- vch += i == 0 ? "-" :
- (s >= 12 ? "+" : " ")
- }
-//fixme: no inversion
- chnm[k] = [ vch ]
- }
- } else {
- chnm = abc2svg.ch_names
+ chnm[k] = []
+ for (i = 0; i < cfmt.chord.names[k].length; i++)
+ chnm[k].push(+cfmt.chord.names[k][i])
+ }
}
// 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)
+ if (k == 9)
k++ // skip the channel 10
// create the chord voice
vch = {
v: voice_tb.length,
@@ -235,46 +305,75 @@
type: C.BLOCK,
subtype: "midiprog",
chn: k + 1,
instr: cfmt.chord.prog || 0,
time: 0,
- ts_prev: first,
- ts_next: first.ts_next
+ dur: 0
},
vol: cfmt.chord.vol || .6 // (external default 76.2)
}
vch.sym.p_v = vch
vch.sym.v = vch.v
vch.last_sym = vch.sym
voice_tb.push(vch)
- first.ts_next = vch.sym
+
+ s = first
+ bld_rhy(cfmt.chord.rhy // chord rhythm
+ || meterhy(s.p_v.meter))
+
+ // insert the MIDI program of the chord voice after the tempo
+ while (s.type != C.TEMPO && !s.dur)
+ s = s.ts_next
+ vch.sym.ts_prev = s
+ vch.sym.ts_next = s.ts_next
+ s.ts_next.ts_prev = vch.sym
+ s.ts_next = vch.sym
+
+ s_ch = { // chord template
+// v: vch.v,
+// p_v: vch,
+// type: C.NOTE,
+ notes: []
+ }
// loop on the symbols and add the accompaniment chords
gchon = cfmt.chord.gchon
+ ti = 0 // time index in rhy
s = first
while (1) {
- if (!s.ts_next) {
- if (gchon)
- vch.last_sym.dur = s.time - vch.last_sym.time
- break
- }
- s = s.ts_next
- if (!s.a_gch) {
- if (s.subtype == "midigch") {
- if (gchon && !s.on)
- vch.last_sym.dur = s.time - vch.last_sym.time
- gchon = s.on
- }
- continue
- }
- if (!gchon)
- continue
- for (i = 0; i < s.a_gch.length; i++) {
- gch = s.a_gch[i]
- if (gch.type != 'g')
- continue
- vch.last_sym.dur = s.time - vch.last_sym.time
- gench(s)
- break
- }
- }
+ if (gchon && rhy != '+') {
+ while (s.time > nextim) {
+ insch(nextim) // generate the rhythm
+ nextim += dt
+ }
+ }
+ if (s.a_gch) {
+ for (i = 0; i < s.a_gch.length; i++) {
+ if (s.a_gch[i].type != 'g')
+ continue
+ gench(s, i)
+ if (rhy == '+')
+ insch(s.time) // no rhythm, start now
+ break
+ }
+ }
+ if (!s.dur) {
+ if (s.bar_num) { // if measure bar
+ ti = 0 // reset the time index
+ } else if (s.wmeasure) { // if meter
+ md = s.wmeasure
+ if (rhy != '+')
+ bld_rhy(meterhy(s))
+ } else if (s.subtype == "midigch") {
+ if (s.on != undefined)
+ gchon = s.on
+ else
+ bld_rhy(s.rhy) // new rhythm
+ }
+ }
+ if (!s.ts_next)
+ break
+ s = s.ts_next
+ }
+ if (gchon)
+ set_dur(vch.last_sym, s.time)
} // chord()
Index: util/follow.js
==================================================================
--- util/follow.js
+++ util/follow.js
@@ -37,11 +37,11 @@
abc.out_svg('" width="' + w.toFixed(2) +
'" height="' + abc.sh(h).toFixed(2) + '"/>\n')
}
playconf.onnote = function(i, on) {
- var b, i, e, elts,
+ var b, e, elts,
x = 0,
y = 0
if (abc2svg.mu) // if many tunes with same offsets
elts = abc2svg.mu.d.getElementsByClassName('_' + i + '_')
Index: util/play.js
==================================================================
--- util/play.js
+++ util/play.js
@@ -1,8 +1,8 @@
// snd-1.js - file to include in html pages with abc2svg-1.js for playing
//
-// Copyright (C) 2015-2021 Jean-Francois Moine
+// Copyright (C) 2015-2024 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
@@ -79,11 +79,12 @@
play2()
} // play()
// if set, out contains an array of the MIDI output ports
function play2(out) {
- var o
+ var o,
+ n = window.sessionStorage && sessionStorage.getItem("audio")
if (!out)
out = []
o = audio5.get_outputs() // get the HTML5 audio port
if (o)
@@ -94,10 +95,16 @@
return
}
if (out.length == 1) {
o = 0 // only one port
} else {
+ if (n) { // if port selected in a previous page
+ for (o = 0; o < out.length; o++)
+ if (out[o] == n)
+ break
+ }
+ if (!n || o >= out.length) {
o = -1 // ask which port?
var pr = "Use"
for (var i = 0; i < out.length; i++)
pr += "\n " + i + ": " + out[i]
var res = window.prompt(pr, '0')
@@ -109,10 +116,15 @@
if (!res || o < 0) {
if (conf.onend)
conf.onend()
return
}
+
+ // memorize the output port for the next pages of the same session
+ if (window.sessionStorage)
+ sessionStorage.setItem("audio", out[o])
+ }
}
// set the current output changing the play functions
current = out[o] == 'sf2' ? audio5 : midi5;
abcplay.play = current.play;
Index: util/sndaud.js
==================================================================
--- util/sndaud.js
+++ util/sndaud.js
@@ -1,8 +1,8 @@
// sndaud.js - audio output using HTML5 audio
//
-// Copyright (C) 2019-2023 Jean-Francois Moine
+// Copyright (C) 2019-2024 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
@@ -130,26 +130,27 @@
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 = sf2par.instrument,
+ var generator,
+ 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 =
+ generator =
sf2par.createInstrumentGenerator_(zone, j)
// instrumentModulator =
// sf2par.createInstrumentModulator_(zone, j)
info.push({
- generator: instrumentGenerator.generator,
+ generator: generator.generator,
// modulator: instrumentModulator.modulator
})
j++
}
//console.log('created instr: '+instrument[i].instrumentName)
@@ -158,12 +159,19 @@
info: info
}
} // get_instr()
// sf2_create
- var i, j, k, sid, gen, parm, gparm, sample, infos,
- sampleRate, scale,
+ var i, j, k, sid, gen, parm, sample, infos,
+ sample_hdr, scale, tune,
+ gparm = { // default parameters
+ attack: .01,
+ hold: .01,
+ decay: .01,
+// sustain: 0,
+// release: .01,
+ },
b = instr >> 7, // bank
p = instr % 128, // preset
pr = sf2pre
rates[instr] = []
@@ -180,86 +188,72 @@
errmsg('unknown instrument ' + b + ':' + p)
return // unknown preset!
}
pr = pr.info // list of gen/mod
for (k = 0; k < pr.length; k++) {
- if (!pr[k].generator.instrument)
+ gen = pr[k].generator
+//fixme: there may be generator definitions here
+//fixme: what when many instruments?
+ if (!gen.instrument)
continue
- gparm = null
- infos = get_instr(pr[k].generator.instrument.amount).info
+ infos = get_instr(gen.instrument.amount).info
for (i = 0; i < infos.length; i++) {
gen = infos[i].generator
- if (!gparm) {
- parm = gparm = { // default parameters
- attack: .001,
- hold: .001,
- decay: .001,
- sustain: 0
-// release: .001
- }
- } else {
- parm = Object.create(gparm) // new parameters
- if (!gen.sampleID)
- gparm = parm // global para,eters
- }
+ // check if already a generator for this key range
+ j = gen.keyRange.lo
+ parm = params[instr][j]
+ parm = Object.create(parm || gparm)
if (gen.attackVolEnv)
parm.attack = Math.pow(2,
gen.attackVolEnv.amount / 1200)
if (gen.holdVolEnv)
parm.hold = Math.pow(2,
gen.holdVolEnv.amount / 1200)
if (gen.decayVolEnv)
parm.decay = Math.pow(2,
- gen.decayVolEnv.amount / 1200) / 3
+// gen.decayVolEnv.amount / 1200) / 3
+ gen.decayVolEnv.amount / 1200) / 4
if (gen.sustainVolEnv)
- parm.sustain = gen.sustainVolEnv.amount / 1000
+ parm.sustain = gen.sustainVolEnv.amount / 10 // in dB
// if (gen.releaseVolEnv)
// parm.release = Math.pow(2,
// gen.releaseVolEnv.amount / 1200)
+ if (gen.initialAttenuation)
+ parm.atte = gen.initialAttenuation / 10 // in dB
if (gen.sampleModes && gen.sampleModes.amount & 1)
parm.sm = 1
- if (!gen.sampleID) // (empty generator!)
- continue
-
+ if (gen.sampleID) {
sid = gen.sampleID.amount
- sampleRate = sf2par.sampleHeader[sid].sampleRate
+ sample_hdr = sf2par.sampleHeader[sid]
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_hdr.sampleRate)
sample_cp(parm.buffer, sample)
if (parm.sm) {
- parm.loopStart = sf2par.sampleHeader[sid].startLoop /
- sampleRate
- parm.loopEnd = sf2par.sampleHeader[sid].endLoop /
- sampleRate
+ parm.loopStart = sample_hdr.startLoop /
+ sample_hdr.sampleRate
+ parm.loopEnd = sample_hdr.endLoop /
+ sample_hdr.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 +
- sf2par.sampleHeader[sid].pitchCorrection / 100 -
+ sample_hdr.pitchCorrection / 100 -
(gen.overridingRootKey ?
gen.overridingRootKey.amount :
- sf2par.sampleHeader[sid].originalPitch)
+ sample_hdr.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
@@ -441,10 +435,14 @@
+ (s.val << 14)
break
case 7: // volume
s.p_v.vol = s.val / 127
break
+ case 10: // pan
+ s.p_v.pan = s.val / 63.5 - 1
+ // ((0) -1: left, (127) 1:right, (64) 0)
+ 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)
@@ -475,10 +473,11 @@
// @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,
+ t2 = t,
c = po.v_c[s.v],
instr = po.c_i[c],
k = key | 0,
parm = params[instr][k],
o = po.ac.createBufferSource(),
@@ -486,11 +485,11 @@
if (!v // mute voice
|| !parm) // if the instrument could not be loaded
return // or if it has not this key
o.buffer = parm.buffer
- if (parm.loopStart) {
+ if (parm.sm) {
o.loop = true
o.loopStart = parm.loopStart
o.loopEnd = parm.loopEnd
}
if (o.detune) {
@@ -500,26 +499,43 @@
}
// o.playbackRate.setValueAtTime(parm.rate, ac.currentTime)
o.playbackRate.value = po.rates[instr][k]
g = po.ac.createGain()
- if (parm.hold < 0.002) {
- g.gain.setValueAtTime(v, t)
- } else {
- if (parm.attack < 0.002) {
- g.gain.setValueAtTime(v, t)
- } else {
- g.gain.setValueAtTime(0, t)
- g.gain.linearRampToValueAtTime(v, t + parm.attack)
- }
- g.gain.setValueAtTime(v, t + parm.hold)
- }
-
- g.gain.exponentialRampToValueAtTime(parm.sustain * v,
- t + parm.decay)
-
- o.connect(g)
+
+ if (parm.atte) // if initial attenuation
+ v /= Math.pow(10, parm.atte / 20)
+ if (parm.hold <= .01) {
+ g.gain.setValueAtTime(v, t)
+ } else {
+ if (parm.attack <= .01) {
+ g.gain.setValueAtTime(v, t)
+ } else {
+ g.gain.setValueAtTime(.01, t)
+ t2 += parm.attack
+ g.gain.linearRampToValueAtTime(v, t2)
+ }
+ t2 += parm.hold
+ g.gain.setValueAtTime(v, t2)
+ }
+
+ if (parm.sustain && parm.decay > .01) {
+ v = parm.sustain == 100
+ ? .01 // 100dB -> full attenuation
+ : v / Math.pow(10, parm.sustain / 20)
+ g.gain.exponentialRampToValueAtTime(v, t2 + parm.decay)
+ }
+
+ if (s.p_v.pan != undefined) { // (control 10)
+ var p = po.ac.createStereoPanner()
+ p.pan.value = s.p_v.pan
+
+ o.connect(p)
+ p.connect(g)
+ } else {
+ o.connect(g)
+ }
g.connect(po.gain)
// start the note
o.start(t)
o.stop(t + d)
Index: util/sndgen.js
==================================================================
--- util/sndgen.js
+++ util/sndgen.js
@@ -29,10 +29,25 @@
// - in the notes[] of NOTE
// s.notes[i].midi
if (!abc2svg)
var abc2svg = {}
+
+// tempo table
+ abc2svg.tmp_tb = {
+ grave: 30,
+ lento: 40,
+ largo: 50,
+ adagio: 66,
+ andante: 86,
+ moderato: 96,
+ allegretto: 112,
+ allegro: 130,
+ vivace: 150,
+ presto: 180,
+ prestissimo: 210
+ }
function ToAudio() {
return {
// generate the play data of a tune
@@ -42,11 +57,11 @@
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,
+ i, n, dt, d, v, s_m,
s = first,
rst = s, // left repeat (repeat restart)
rst_fac, // play factor on repeat restart
rsk = [], // repeat variant array (repeat skip)
b_tim, // time of last measure bar
@@ -217,10 +232,33 @@
first.p_s[i] = s
}
}
}
} // build_parts()
+
+ // update the time linkage when the start time has changed
+ function relink(s, dt) {
+ var tim = s.time + dt, // new time
+ s2 = s.ts_next
+
+ s.ts_prev.ts_next = s2 // remove from the time linkage
+ s2.ts_prev = s.ts_prev
+
+ while (!s2.seqst && s2.ts_next)
+ s2 = s2.ts_next
+ if (s2.time < s.time + dt) // don't move after the next time seq.
+ dt = s2.time - s.time
+ s.time += dt // update time and duration
+ s.dur -= dt
+ s.ts_prev = s2.ts_prev // update the time linkage
+ s.ts_prev.ts_next = s
+ s.ts_next = s2
+ s2.ts_prev = s
+ s.seqst = 1 //true
+ if (s2.time == s.time)
+ s2.seqst = 0 //false
+ } // relink()
// generate the grace notes
function gen_grace(s) {
var g, i, n, t, d, s2,
next = s.next
@@ -239,12 +277,11 @@
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
+ relink(next, d)
}
//fixme: assume the grace notes in the sequence have the same duration
n = 0
for (g = s.extra; g; g = g.next)
n++
@@ -257,16 +294,37 @@
}
} // gen_grace()
// change the tempo
function set_tempo(s) {
+ if (!s.tempo && !s.new_beat) {
+ var p = (s.tempo_str1 || s.tempo_str2)
+ if (!p)
+ return play_fac // no change
+ var upm = abc2svg.tmp_tb[p.toLowerCase()]
+
+ if (!upm)
+ return play_fac // no change
+ upm *= C.BLEN / 240 // (/4/60)
+ if (!s_m.a_meter)
+ return upm
+ if (!s_m.a_meter[0].bot)
+ return (s_m.a_meter[1] && s_m.a_meter[1].top == '|')
+ ? upm * 2 : upm
+ if (s_m.a_meter[0].bot == "8"
+ && !(s_m.a_meter[0].top % 3))
+ return upm / 2 * 3
+ return upm * s_m.a_meter[0].bot / 4
+ }
var i,
d = 0,
n = s.tempo_notes.length
for (i = 0; i < n; i++)
d += s.tempo_notes[i]
+ if (s.new_beat)
+ return play_fac * s.new_beat / d
return d * s.tempo / 60
} // set_tempo()
function set_variant(s) {
var d,
@@ -291,11 +349,13 @@
// if some chord stuff, set the accompaniment data
if (cfmt.chord)
abc2svg.chord(first, voice_tb, cfmt)
// if %%playbeats, create the sounds
- if (cfmt.playbeats)
+ s_m = first.p_v.meter // current meter
+ if (cfmt.playbeats
+ && first.p_v.meter.wmeasure != 1) // if not M:none
def_beats()
if (s.parts)
build_parts(s)
@@ -341,10 +401,11 @@
break
b_typ |= 2
s.rep_p = rst // :| to |:
if (rst == rsk[0])
s.rep_v = rsk // to know the number of variants
+ rst = s // possible restart (..:|..:|)
}
// 1st time repeat
if (s.text) {
if (s.text[0] == '1') {
@@ -402,13 +463,15 @@
}
d /= play_fac
s.pdur = d
v = s.v
break
+ case C.METER:
+ s_m = s // current meter
+ break
case C.TEMPO:
- if (s.tempo)
- play_fac = set_tempo(s)
+ play_fac = set_tempo(s)
break
}
s = s.ts_next
} // loop
} // add()
@@ -505,36 +568,45 @@
s = {
subtype: "midictl",
p_v: p_v,
v: s2.v
}
+
+ p_v.vol = p_v.pan = undefined // reset the controllers
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 != s2; s = s.next) {
- if (s.subtype == "midictl")
+ if (s.subtype == "midictl") {
po.midi_ctrl(po, s, t)
- else if (s.subtype == 'midiprog')
+ } else if (s.subtype == 'midiprog') {
+ po.v_c[s.v] = s.chn
+ if (s.instr != undefined)
+ po.c_i[po.v_c[s.v]] = s.instr
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
-
+ while (p_v.voice_down) {
+ p_v = p_v.voice_down
+ po.v_c[p_v.v] = i
+ }
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,
+ var d, i, st, m, note, g, s2, t, maxt, now, p_v,
C = abc2svg.C,
s = po.s_cur
// search the end of a sequence of variants
function var_end(s) {
@@ -602,10 +674,12 @@
po.repn = true
} else {
if (s.rep_v)
s2 = var_end(s)
po.repn = false
+ if (s.bar_type.slice(-1) == ':') // if ::
+ po.repv = 1
}
}
if (s.rep_s) { // first variant
s2 = s.rep_s[po.repv] // next variant
if (s2) {
@@ -661,14 +735,23 @@
if (s && s != po.s_end && !s.noplay) {
switch (s.type) {
case C.BAR:
break
case C.BLOCK:
- if (s.subtype == "midictl")
+ if (s.subtype == "midictl") {
po.midi_ctrl(po, s, t)
- else if (s.subtype == 'midiprog')
+ } else if (s.subtype == 'midiprog') {
+ po.v_c[s.v] = s.chn
+ if (s.instr != undefined)
+ po.c_i[po.v_c[s.v]] = s.instr
po.midi_prog(po, s)
+ p_v = s.p_v
+ while (p_v.voice_down) {
+ p_v = p_v.voice_down
+ po.v_c[p_v.v] = s.chn
+ }
+ }
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++) {
Index: util/sndmid.js
==================================================================
--- util/sndmid.js
+++ util/sndmid.js
@@ -1,8 +1,8 @@
// sndmid.js - audio output using HTML5 MIDI
//
-// Copyright (C) 2019-2023 Jean-Francois Moine
+// Copyright (C) 2019-2024 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
@@ -47,17 +47,51 @@
function Midi5(i_conf) {
var po,
conf = i_conf, // configuration
empty = function() {},
+ det_tb, // detune table
rf, // get_outputs result function
op // output MIDI port
// return the play real time in seconds
function get_time(po) {
return window.performance.now() / 1000
} // get_time()
+
+ var mutone = function(k, a, t) {
+ if (!det_tb) {
+ if (!a)
+ return // no microtone
+ det_tb = {}
+ }
+ if (!Midi5.ma.sysexEnabled) { // no SysEx
+ mutone = empty // don't come back here anymore
+ return
+ }
+ var old_a = det_tb[k] // previous detune of 'k'
+
+ if ((!old_a && !a)
+ || old_a == a)
+ return // same detune
+ det_tb[k] = a
+
+ a *= 163.84 // 16384 / 100
+ po.op.send(new Uint8Array([
+ 0xf0, 0x7f, // realtime SysEx
+ 0x7f, // all devices
+ 0x08, // MIDI tuning
+ 0x02, // note change
+ 0, // tuning prog number
+ 0x01, // number of notes
+ k, // key
+ k, // semitone
+ (a >> 7) & 0x7f, // fraction of semitone
+ a & 0x7f,
+ 0xf7 // SysEx end
+ ]), t)
+ } // mutone()
// create a note
// @po = play object
// @s = symbol
// @k = MIDI key + detune
@@ -70,28 +104,14 @@
i = po.c_i[c]
k |= 0 // remove the detune value
t *= 1000 // convert to ms
- d *= 1000
-
- 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
- i & 0x7f, // tuning prog number
- 0x01, // number of notes
- k, // key
- k, // note
- a / .78125, // MSB fract
- 0, // LSB fract
- 0xf7 // SysEx end
- ]), t)
- }
+ d *= 1000
+
+ mutone(k, a, t) // handle microtone accidentals
+
po.op.send(new Uint8Array([0x90 + c, k, 127]), t) // note on
po.op.send(new Uint8Array([0x80 + c, k, 0]), t + d - 20) // note off
} // note_run()
// send a MIDI control
@@ -163,18 +183,11 @@
// open MIDI with SysEx
navigator.requestMIDIAccess({sysex: true}).then(
send_outputs,
function(msg) {
-
- // open MIDI without SysEx
- navigator.requestMIDIAccess().then(
- send_outputs,
- function(msg) {
- rf()
- }
- )
+ rf()
}
)
}, // get_outputs()
// set the output port
@@ -216,26 +229,10 @@
// 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
- 0x08, // MIDI tuning standard
- 0x02, // note change
- 0x00, // tuning prog number
- 0x01, // number of notes
- 0x69, // key
- 0x69, // note
- 0x00, // MSB fract
- 0, // LSB fract
- 0xf7 // SysEx end
- ]), t)
-}
abc2svg.play_next(po)
}, // play()
// stop playing
ADDED version.txt
Index: version.txt
==================================================================
--- /dev/null
+++ version.txt
@@ -0,0 +1,1 @@
+abc2svg.version="v1.22.23";abc2svg.vdate="2024-12-14"