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 += '")
+			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\ +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\ +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(p.slice(0, i) + + '\n y="' + yo + '"' + + p.slice(i)) + yo += +h[2] // offset next SVG + break + default: + if (p.slice(-6) == "") + 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('\n' + + out.join('\n') + + '\n') + 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 = '
'
+			if (f && outb) {
+				o = f.indexOf('edit') > 0
+					? " contentEditable>" : ">"
+				outb = '
\n\
 
\n' + 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"