Index: README.md ================================================================== --- README.md +++ README.md @@ -326,25 +326,27 @@ Example: `        abcmjs toabw.js my_file.abc > my_file.abw` - `tomei.js` This script outputs the music as a [MEI](https://music-encoding.org/) file. - Note, only one tune may be translated from ABC to MEI (multi-tunes ABC - generates bad MEI). + Note, only one tune may be translated from ABC to MEI (multi-tunes ABC files + generate bad MEI files). - `tonotes.js` This script outputs a list of the MIDI events. - `toodt.js` This script creates an Open Document (ODT+SVG) which can be read by most word processors (abiword, libreoffice...). - It runs only with the shell script `abc2svg` and asks for the npm module - `jszip` to be installed. + When runs with the shell script `abc2svg`, it asks for the npm module + `jszip` to be installed. + When run with `abcqjs` on unix-like systems, it creates a temporary + directory tree in `/tmp`. The output ODT document may be specified in the command line argument after `-o` (default `abc.odt`). Example: - `        abc2svg toodt.js my_file.abc -o my_file.odt` + `        abcqjs toodt.js my_file.abc -o my_file.odt` - `toparam.js` This script just outputs the abc2svg parameters. #### 2.3 PDF generation @@ -461,11 +463,11 @@ #### 4.3 More about the web scripts Here are the scripts which are used in a web context: - `abcweb-1.js` - This script replaces the ABC or MEI sequences found in the (X)HTML file + This script replaces the ABC sequences found in the (X)HTML file by SVG images of the music. The ABC sequences are searched: - first inside <script> elements with a type ["text/vnd.abc"](https://www.iana.org/assignments/media-types/text/vnd.abc) (the script tag is replaced by a <div>), @@ -539,25 +541,10 @@ - `snd-1.js` This script may be used with `abcweb-1.js` or `abcweb1-1.js` to play the rendered ABC music. -### 5. MEI support - -As an experimental feature, an extended core `mei2svg-1.js` may be generated. -This one may handle both the ABC and -[MEI](https://music-encoding.org/ "Music Encoding Initiative") -notations. - -In browser mode, the script `abcweb-1.js` loads either `abc2svg-1.js` or -`mei2svg-1.js` after checking if `/dev/null 2>&1; then echo "and $c" command $abcscr "$@" > /tmp/o.html - "$c" --headless --disable-gpu\ - --print-to-pdf=$out --print-to-pdf-no-header /tmp/o.html +# --headless=new does not work + "$c" --headless --print-to-pdf=$out --no-pdf-header-footer\ + /tmp/o.html rm /tmp/o.html exit 0 fi done Index: build.ninja ================================================================== --- build.ninja +++ build.ninja @@ -12,10 +12,11 @@ command = sed "s+@MAJOR@+$MAJOR+" $in > $out rule font command = fontforge -c 'open("font/abc2svg.sfd").generate("abc2svg.ttf")';$ fontforge -c 'open("font/abc2svg.sfd").generate("abc2svg.woff")';$ + fontforge -c 'open("font/abc2svg.sfd").generate("abc2svg.woff2")';$ echo '// abc2svg music font' > font.js;$ echo "var musicfont = 'url(\"data:application/octet-stream;base64,\\" >> font.js;$ base64 abc2svg.ttf | sed 's/$$/\\/' >> font.js;$ echo "\") format(\"truetype\")'" >> font.js @@ -92,14 +93,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 mei2svg-$MAJOR.js: jsc core/abc2svg.js core/deco.js core/draw.js $ - font.js core/format.js core/front.js core/music.js core/parse.js core/subs.js $ - core/svg.js core/tune.js core/lyrics.js core/gchord.js core/mei.js core/tail.js $ - core/modules.js | package.json build ambitus-$MAJOR.js: jscu modules/ambitus.js build break-$MAJOR.js: jscu modules/break.js build capo-$MAJOR.js: jscu modules/capo.js build chordnames-$MAJOR.js: jscu modules/chordnames.js @@ -152,11 +149,10 @@ 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 -# not mei2svg-$MAJOR.js default all # fossil new release rule version Index: cmdline.js ================================================================== --- cmdline.js +++ cmdline.js @@ -1,8 +1,8 @@ // abc2svg - cmdline.js - command line // -// Copyright (C) 2014-2021 Jean-Francois Moine +// Copyright (C) 2014-2023 Jean-Francois Moine // // This file is part of abc2svg. // // abc2svg is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -48,11 +48,11 @@ i = file.indexOf('\r') if (i >= 0) { if (file[i + 1] == '\n') file = file.replace(/\r\n/g, '\n') // M$ else - fike = file.replace(/\r/g, '\n') // Mac + file = file.replace(/\r/g, '\n') // Mac } // load the required modules (synchronous) abc2svg.modules.load(file) @@ -80,17 +80,10 @@ return } // if (typeof(utf_convert) == "function") // file = utf_convert(file) - if (fn.slice(-4) == ".mei") { - if (!abc.mei2mus) - abc2svg.abort(new Error("No MEI support")); - abc.mei2mus(file) - return - } - // generate try { abc.tosvg(fn, file) } catch (e) { abc2svg.abort(e) Index: core/abc2svg.js ================================================================== --- core/abc2svg.js +++ core/abc2svg.js @@ -112,31 +112,20 @@ [-2, -1, 0, 1, 2, 0, 0, 1, 2, 3, 4, 0, // E F G 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 0, 5, 6, 7, 8, 9, 0, // A B 7, 8, 9, 10, 11, 0, 9, 10, 11, 12, 13 ]) -abc2svg.b40k = new Int8Array( // base-40 interval to possible transposition -// C D - [36, 1, 2, 3, 8, 2, 2, 7, 8,13,14, 2, -// 37 -// E F G - 8,13,14,19,20,13,14,19,20,25, 2,19,24,25,30,31, 2, -// 20 -// A B - 25,30,31,36,37, 2,31,36,37, 2, 3 ]) -// 2 -abc2svg.b40sf = new Int8Array( // base-40 interval to key signature -// C D - [-2,-7, 0, 7, 2, 88, 0,-5, 2,-3, 4, 88, -// E F G - 2,-3, 4,-1, 6,-3, 4,-1, 6, 1,88,-1,-6, 1,-4, 3,88, -// A B - 1,-4, 3,-2, 5,88, 3,-2, 5, 0, 7 ]) +abc2svg.b40l5 = new Int8Array([ // base-40 to line of fifth +// C D + -14, -7, 0, 7, 14, 0,-12, -5, 2, 9, 16, 0, +// E F G + -10, -3, 4, 11, 18,-15, -8, -1, 6, 13, 0,-13, -6, 1, 8, 15, 0, +// A B + -11, -4, 3, 10, 17, 0, -9, -2, 5, 12, 19 ]) + abc2svg.isb40 = new Int8Array( // interval with sharp to base-40 interval - [0, 1, 6,11,12,17,18,23,28,29,34,35]) -abc2svg.ifb40 = new Int8Array( // interval with flat to base-40 interval - [0, 5, 6,11,12,17,22,23,28,29,34,39]) + [0, 1, 6,7,12,17,18,23,24,29,30,35]) abc2svg.pab40 = function(p, a) { p += 19 // staff pitch from C-1 var b40 = ((p / 7) | 0) * 40 + abc2svg.p_b40[p % 7] if (a && a != 3) // if some accidental, but not natural @@ -177,10 +166,14 @@ "7#5": "+7", "sus": "sus4", "7sus": "7sus4" } // ch_alias +// global fonts +abc2svg.font_tb = [] // fonts - index = font.fid +abc2svg.font_st = {} // font style => font_tb index for incomplete user fonts + // font weight // reference: // https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight abc2svg.ft_w = { thin: 100, @@ -241,11 +234,13 @@ // mask some unsafe functions var require = empty_function, system = empty_function, write = empty_function, - XMLHttpRequest = empty_function; + XMLHttpRequest = empty_function, + std = null, + os = null // -- constants -- // staff system var OPEN_BRACE = 0x01, @@ -419,10 +414,7 @@ error(sev, s, msg, a1, a2, a3, a4) } // inject javascript code function js_inject(js) { - if (!/eval *\(|Function|setTimeout|setInterval/.test(js)) - eval('"use strict";\n' + js) - else - syntax(1, "Unsecure code") + eval('"use strict";\n' + js) } Index: core/deco.js ================================================================== --- core/deco.js +++ core/deco.js @@ -1,8 +1,8 @@ // abc2svg - deco.js - decorations // -// 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 @@ -55,96 +55,96 @@ a_de, // array of the decoration elements cross // cross voice decorations // decorations - populate with standard decorations var decos = { - dot: "0 stc 6 1 1", - tenuto: "0 emb 6 3 3", - slide: "1 sld 3 7 0", - arpeggio: "2 arp 12 10 2", - roll: "3 roll 3,3 6 6", - emphasis: "3 accent 3.5,3.5 4 4", - lowermordent: "3 lmrd 4,4 5 5", - uppermordent: "3 umrd 4,4 5 5", - trill: "3 trl 14 5 5", - upbow: "3 upb 10 5 5", - downbow: "3 dnb 9 5 5", - gmark: "3 grm 6 5 5", - wedge: "0 wedge 8 3 3", // (staccatissimo or spiccato) - longphrase: "3 lphr 0 1 1", - mediumphrase: "3 mphr 0 1 1", - shortphrase: "3 sphr 0 1 1", - turnx: "3 turnx 5,5 6 6", - invertedturn: "3 turn 5,5 6 6", + dot: "0 stc 6 1.5 1", + tenuto: "0 emb 6 4 3", + slide: "1 sld 3 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", + 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", + turnx: "3 turnx 7,2.5 5 6", + invertedturn: "3 turn 7,2 5 6", "0": "3 fng 5,5 3 3 0", "1": "3 fng 5,5 3 3 1", "2": "3 fng 5,5 3 3 2", "3": "3 fng 5,5 3 3 3", "4": "3 fng 5,5 3 3 4", "5": "3 fng 5,5 3 3 5", - plus: "3 dplus 7 3 3", - "+": "3 dplus 7 3 3", - accent: "3 accent 3.5,3.5 4 4", - ">": "3 accent 3.5,3.5 4 4", - marcato: "3 marcato 9 3 3", - "^": "3 marcato 9 3 3", - mordent: "3 lmrd 4,4 5 5", - open: "3 opend 10 3 3", - snap: "3 snap 14 3 3", - thumb: "3 thumb 14 3 3", - turn: "3 turn 5,5 6 6", - "trill(": "3 ltr 8 4 0", - "trill)": "3 ltr 8 4 0", + plus: "3 dplus 8,2 2 4", + "+": "3 dplus 8,2 2 4", + ">": "5 accent 3.5,3.5 4 4", + accent: "5 accent 3.5,3.5 4 4", + emphasis: "5 accent 3.5,3.5 4 4", + marcato: "3 marcato 9 5 5", + "^": "3 marcato 9 5 5", + mordent: "3 lmrd 6,5 4 6", + open: "3 opend 8 3 3", + snap: "3 snap 10 3 3", + thumb: "3 thumb 10 3 3", + turn: "3 turn 7,2.5 5 6", + "trill(": "5 ltr 8 0 0", + "trill)": "5 ltr 8 0 0", "8va(": "5 8va 12 6 6", "8va)": "5 8va 12 6 6", - "8vb(": "7 8vb 12 6 6", - "8vb)": "7 8vb 12 6 6", + "8vb(": "4 8vb 10,5 6 6", + "8vb)": "4 8vb 10,5 6 6", "15ma(": "5 15ma 12 9 9", "15ma)": "5 15ma 12 9 9", - "15mb(": "7 15mb 12 9 9", - "15mb)": "7 15mb 12 9 9", - breath: "5 brth 0 1 20", - caesura: "5 caes 0 1 10", - short: "5 short 0 1 4", - tick: "5 tick 0 1 4", - coda: "5 coda 24 10 10", + "15mb(": "4 15mb 12 9 9", + "15mb)": "4 15mb 12 9 9", + breath: "5 brth 0 1 16", + caesura: "5 caes 0 1 20", + short: "5 short 0 1 16", + tick: "5 tick 0 1 16", + coda: "5 coda 22,5 10 10", dacapo: "5 dacs 16 20 20 Da Capo", dacoda: "5 dacs 16 20 20 Da Coda", - "D.C.": "5 dcap 16 10 10", - "D.S.": "5 dsgn 16 10 10", - "D.C.alcoda": "5 dacs 16 38 38 D.C. al Coda", - "D.S.alcoda": "5 dacs 16 38 38 D.S. al Coda", - "D.C.alfine": "5 dacs 16 38 38 D.C. al Fine", - "D.S.alfine": "5 dacs 16 38 38 D.S. al Fine", - fermata: "3 hld 12 7 7", - fine: "5 dacs 16 10 10 Fine", - invertedfermata: "4 hld 12 7 7", - segno: "5 sgno 22 8 8", - f: "6 f 18 4 4", - ff: "6 ff 18 6 6", - fff: "6 fff 18 9 9", - ffff: "6 ffff 18 12 12", - mf: "6 mf 18 9 9", - mp: "6 mp 18 10 10", - p: "6 p 18 5 5", - pp: "6 pp 18 9 9", - ppp: "6 ppp 18 13 13", - pppp: "6 pppp 18 17 17", - pralltriller: "3 umrd 4,4 5 5", - sfz: "6 sfz 18 8 8", - ped: "7 ped 18 6 10", - "ped-up": "7 pedoff 12 4 4", - "ped(": "7 lped 20 5 5", - "ped)": "7 lped 20 5 5", - "crescendo(": "6 cresc 18 0 0", - "crescendo)": "6 cresc 18 0 0", - "<(": "6 cresc 18 0 0", - "<)": "6 cresc 18 0 0", - "diminuendo(": "6 dim 18 0 0", - "diminuendo)": "6 dim 18 0 0", - ">(": "6 dim 18 0 0", - ">)": "6 dim 18 0 0", + "D.C.": "5 dcap 16,3 6 6", + "D.S.": "5 dsgn 16,3 6 6", + "D.C.alcoda": "5 dacs 16 32 32 D.C. al Coda", + "D.S.alcoda": "5 dacs 16 32 32 D.S. al Coda", + "D.C.alfine": "5 dacs 16 32 32 D.C. al Fine", + "D.S.alfine": "5 dacs 16 32 32 D.S. al Fine", + fermata: "5 hld 12 7.5 7.5", + fine: "5 dacs 16 12 12 Fine", + invertedfermata: "7 hld 12 8 8", + segno: "5 sgno 22,2 5 5", + f: "6 f 12,5 3 4", + ff: "6 ff 12,5 8 5", + fff: "6 fff 12,5 11 9", + ffff: "6 ffff 12,5 15 12", + mf: "6 mf 12,5 8 10", + mp: "6 mp 12,5 9 10", + p: "6 p 12,5 3 6", + pp: "6 pp 12,5 8 9", + ppp: "6 ppp 12,5 14 11", + pppp: "6 pppp 12,5 14 17", + pralltriller: "3 umrd 6,5 4 6", + sfz: "6 sfz 12,5 9 9", + ped: "6 ped 9 6 10", + "ped-up": "6 pedoff 9 4 4", + "ped(": "7 lped 14 1 1", + "ped)": "7 lped 14 1 1", + "crescendo(": "6 cresc 15,2 0 0", + "crescendo)": "6 cresc 15,2 0 0", + "<(": "6 cresc 15,2 0 0", + "<)": "6 cresc 15,2 0 0", + "diminuendo(": "6 dim 15,2 0 0", + "diminuendo)": "6 dim 15,2 0 0", + ">(": "6 dim 15,2 0 0", + ">)": "6 dim 15,2 0 0", "-(": "8 gliss 0 0 0", "-)": "8 gliss 0 0 0", "~(": "8 glisq 0 0 0", "~)": "8 glisq 0 0 0", // internal @@ -189,11 +189,11 @@ d_upstaff // 4 (below the staff) ], f_staff = [ null, null, null, null, null, d_upstaff, // 5 (above the staff) - d_pf, // 6 - tied to staff (dynamic marks) + d_upstaff, // 6 - tied to staff (dynamic marks) d_upstaff // 7 (below the staff) ] /* -- get the max/min vertical offset -- */ function y_get(st, up, x, w) { @@ -326,120 +326,17 @@ // de.x = s.x - xc; de.x -= xc; de.y = 3 * ((s.notes[0].pit + s.notes[s.nhd].pit) / 2 - 18) - h / 2 - 3 } -// special case for long dynamic decorations -function d_cresc(de) { - if (de.ldst) // skip start of deco - return - var up, dx, x2, i, de3, - dd = de.dd, - s2 = de.s, - de2 = de.start, // start of the deco - s = de2.s, - x = s.x + 3 // x left - - de.st = s2.st; - de.lden = false; /* old behaviour */ - de.has_val = true; - if (dd.ty == '^') - up = 1 - else if (dd.ty != '_') - up = up6(s2, s2.pos.dyn) - if (up) - de.up = true - - // shift the starting point if any dynamic mark on this symbol - i = de2.ix - while (--i >= 0) { - de3 = a_de[i] - if (!de3 || de3.s != s) - break - } - while (1) { - i++ - de3 = a_de[i] - if (!de3 || de3.s != s) - break - if (de3 == de || de3 == de2) - continue - if (!(de.up ^ de3.up) - && f_staff[de3.dd.func]) { // if dynamic mark -// x2 = de3.x + de3.val + 4 - x2 = de3.x + de3.dd.wr + 2 - if (x2 > x) - x = x2 - break - } - } - - if (de.defl.noen) { /* if no decoration end */ - dx = de.x - x - if (dx < 20) { - x = de.x - 20 - 3; - dx = 20 - } - } else { - - // shift the ending point if any dynamic mark - x2 = s2.x - i = de.ix - while (--i > 0) { - de3 = a_de[i] - if (!de3 || de3.s != s2) - break - } - while (1) { - i++ - de3 = a_de[i] - if (!de3 || de3.s != s2) - break - if (de3 == de || de3 == de2) - continue - if (!(de.up ^ de3.up) - && f_staff[de3.dd.func]) { // if dynamic mark - x2 -= de3.dd.wl - break - } - } - dx = x2 - x - 4 - if (dx < 20) { - x -= (20 - dx) * .5; - dx = 20 - } - } - - de.val = dx; - de.x = x; - de.y = y_get(de.st, up, x, dx) - if (!up) - de.y -= dd.h - else - de.y += dd.hd - /* (y_set is done later in draw_deco_staff) */ -} - /* 0: near the note (dot, tenuto) */ function d_near(de) { - var y, up, + var y, + up = de.up, s = de.s, dd = de.dd - if (dd.str) { // annotation like decoration -// de.x = s.x; -// de.y = s.y; - return - } - if (dd.ty == '^') - up = 1 - else if (dd.ty == '_') - up = 0 - else if (s.multi) - up = s.multi > 0 - else - up = s.stem < 0 y = up ? s.ymx : s.ymn if (y > 0 && y < 24) { y = (((y + 9) / 6) | 0) * 6 - 6 // between lines } if (up) { @@ -451,10 +348,11 @@ s.ymn = y } else { y -= dd.h s.ymn = y - dd.hd } + 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 @@ -475,59 +373,10 @@ } } } } -/* 6: dynamic marks */ -function d_pf(de) { - var dd2, x2, x, up, - s = de.s, - dd = de.dd, - de_prev; - - // don't treat here the long decorations - if (de.ldst) // if long deco start - return - if ((de.pos & C.SL_ALI_MSK) == C.SL_CLOSE - || ((de.pos & C.SL_ALI_MSK) == 0 - && s.fmt.dynalign < 0)) { // if no dynalign - d_upstaff(de) // then, do as above/below the staff - return - } - if (de.start) { // if long decoration - d_cresc(de) - return - } - - de.val = dd.wl + dd.wr; - up = up6(s, de.pos) - if (up) - de.up = true; - x = s.x - if (de.ix > 0) { - de_prev = a_de[de.ix - 1] - if (de_prev.s == s - && ((de.up && !de_prev.up) - || (!de.up && de_prev.up))) { - dd2 = de_prev.dd - if (f_staff[dd2.func]) { /* if dynamic mark */ - x2 = de_prev.x + de_prev.val + 4; - if (x2 > x) - x = x2 - } - } - } - - de.x = x; - de.y = y_get(s.st, up, x - dd.wl, de.val) - if (!up) - de.y -= dd.h - else - de.y += dd.hd - /* (y_set is done later in draw_deco_staff) */ -} - /* 1: special case for slide */ function d_slide(de) { var m, dx, s = de.s, yc = s.notes[0].pit, @@ -558,62 +407,101 @@ // special case for long decoration function d_trill(de) { if (de.ldst) return - var up, y, w, tmp, + var y, w, tmp, dd = de.dd, de2 = de.prev, + up = de.start.up, s2 = de.s, st = s2.st, s = de.start.s, x = s.x + // shift the starting point of a long decoration + // in the cases "T!trill(!" and "!pp!!<(!" + // (side effect on x) + function sh_st() { + var de3, + de2 = de.start, // start of the decoration + s = de2.s, + i = de2.ix // index of the current decoration + + while (--i >= 0) { + de3 = a_de[i] + if (!de3 || de3.s != s) + break + } + while (1) { // loop on the decorations of the symbol + i++ + de3 = a_de[i] + if (!de3 || de3.s != s) + break + if (de3 == de2) + continue + if (!(up ^ de3.up) + && (de3.dd.name == "trill" + || de3.dd.func == 6)) { // dynamic + x += de3.dd.wr + 2 + break + } + } + } // sh_st() + + // shift the ending point of a long decoration + // (side effect on w) + function sh_en() { + var de3, + i = de.ix // index of the current decoration + + while (--i > 0) { + de3 = a_de[i] + if (!de3 || de3.s != s2) + break + } + while (1) { // loop on the decorations of the symbol + i++ + de3 = a_de[i] + if (!de3 || de3.s != s2) + break +// if (de3 == de || de3 == de2) + if (de3 == de) + continue + if (!(up ^ de3.up) + && de3.dd.func == 6) { // if dynamic mark + w -= de3.dd.wl + break + } + } + } //sh_en() + + // d_trill() if (de2) { // same height x = de2.s.x + de.dd.wl + 2 de2.val -= de2.dd.wr if (de2.val < 8) de2.val = 8 } de.st = st - - switch (dd.func) { - case 5: - up = 1 - break - case 4: - case 7: - break - case 3: // tied to note - case 6: // dynamic mark - if (dd.func == 6) - up = up6(s, de.pos) - else - up = up3(s, de.pos) - break - } - if (dd.ty == '^') - up = 1 - else if (dd.ty == '_') - up = 0 + de.up = up + + sh_st() // shift the starting point? if (de.defl.noen) { /* if no decoration end */ w = de.x - x if (w < 20) { x = de.x - 20 - 3; w = 20 } } else { - w = s2.x - x - 6 - if (s2.type == C.NOTE) - w -= 6 - if (w < 10) { - x -= 10 - w - w = 10 - } - } - y = y_get(st, up, x - dd.wl - 5, w) + w = s2.x - x - 4 + sh_en(de) // shift the ending point? + if (w < 20) + w = 20 + } + y = y_get(st, up, x - dd.wl, w) if (up) { tmp = staff_tb[s.st].topbar + 2 if (y < tmp) y = tmp } else { @@ -641,13 +529,14 @@ de.lden = false; de.has_val = true; de.val = w; de.x = x; de.y = y - de.up = up if (up) y += dd.h; + else + y -= dd.hd y_set(st, up, x, w, y) if (up) s.ymx = s2.ymx = y else s.ymn = s2.ymn = y @@ -662,17 +551,16 @@ if (de.start) { // if long decoration d_trill(de) return } - var y, up, inv, + var y, inv, + up = de.up, s = de.s, dd = de.dd, x = de.x, - w = dd.wl + dd.wr, - stafft = staff_tb[s.st].topbar + 2 + dd.hd, - staffb = staff_tb[s.st].botbar - 2 - dd.h + w = dd.wl + dd.wr // glyphs inside the staff switch (dd.glyph) { case "brth": case "caes": @@ -679,15 +567,15 @@ case "lphr": case "mphr": case "sphr": case "short": case "tick": - y = stafft + y = staff_tb[s.st].topbar + 2 + dd.hd if (s.type == C.BAR) { s.invis = 1 } else { - if (dd.glyph == "brth" && y < s.ymx) + if (dd.glyph == "brth" && y < s.ymx) y = s.ymx for (s = s.ts_next; s; s = s.ts_next) if (s.seqst) break x += ((s ? s.x : realwidth) - x) * .45 @@ -697,13 +585,10 @@ return } if (s.nhd) x += s.notes[s.stem >= 0 ? 0 : s.nhd].shhd; - de.up = up = (dd.func == 4 || dd.func == 7) ? 0 - : dd.func == 6 ? up6(s, de.pos) - : up3(s, de.pos) switch (dd.ty) { case '@': case '<': case '>': @@ -712,20 +597,16 @@ } if (y == undefined) { if (up) { y = y_get(s.st, true, x - dd.wl, w) + dd.hd - if (y < stafft) - y = stafft if (de.y > y) y = de.y s.ymx = y + dd.h } else { y = y_get(s.st, false, x - dd.wl, w) - dd.h - if (y > staffb) - y = staffb if (de.y < y) y = de.y if (dd.name == "fermata" || dd.glyph == "accent" || dd.glyph == "roll") @@ -732,16 +613,24 @@ de.inv = 1 s.ymn = y - dd.hd } } - if (y >= stafft) - y_set(s.st, 1, x - dd.wl, w, y + dd.h) - else if (y <= staffb) - y_set(s.st, 0, x - dd.wl, w, y - dd.hd) + if (dd.wr > 5 && x > realwidth - dd.wr) + de.x = x = realwidth - dd.wr - de.x = x; +// if (dd.func == 6 +// && ((de.pos & C.SL_ALI_MSK) == C.SL_ALIGN +// || ((de.pos & C.SL_ALI_MSK) == 0 +// && de.s.fmt.dynalign > 0))) // if align +// ; +// else + if (up) + y_set(s.st, 1, x - dd.wl, w, y + dd.h) + else + y_set(s.st, 0, x - dd.wl, w, y - dd.hd) + de.y = y } // add a decoration /* syntax: @@ -828,14 +717,19 @@ dd.wr = wr; str = text.replace(a[0], '').trim() if (str) { // optional string if (str[0] == '"') str = str.slice(1, -1); - if (str[0] == '@' - && !str.match(/^@([0-9.-]+),([0-9.-]+);?/)) { + if (str[0] == '@') { + c = str.match(/^@([0-9.-]+),([0-9.-]+);?/) + if (!c) { error(1, null, "%%deco: bad position '$1'", str) return + } + dd.dx = +c[1] // x and y offsets + dd.dy = +c[2] + str = str.replace(c[0], '') } dd.str = str } /* compatibility */ @@ -930,11 +824,11 @@ return dd } // get_dd() /* -- convert the decorations -- */ function deco_cnv(s, prev) { - var j, dd, nm, note, s1, court + var i, j, dd, nm, note, s1, court while (1) { nm = a_dcn.shift() if (!nm) break @@ -955,10 +849,23 @@ // if (s.type != C.NOTE && s.type != C.REST) { if (!s.notes) { error(1, s, errs.must_note_rest, nm) continue } + break + case 4: // below the staff + case 5: // above the staff + i = nm.match(/1?[85]([vm])([ab])([()])/) + if (i) { // if ottava + j = i[1] == 'v' ? 1 : 2 + if (i[2] == 'b') + j = -j + if (!s.ottava) + s.ottava = [] + s.ottava[i[3] == '(' ? 0 : 1] = j + glovar.ottava = 1 //true + } break case 8: // gliss if (s.type != C.NOTE) { error(1, s, errs.must_note, nm) continue @@ -1062,10 +969,14 @@ break case 41: /* rbend */ s.rbstop = 2 // with end break case 42: // editorial + if (s.type != C.NOTE) { + error(1, s, errs.must_note, nm) + continue + } if (!s.notes[0].acc) continue nm = "sacc" + s.notes[0].acc.toString() // small accidental dd = dd_tb[nm] if (!dd) { @@ -1077,16 +988,24 @@ } delete s.notes[0].acc curvoice.acc[s.notes[0].pit + 19] = 0 // ignore the accidental break case 43: // courtesy + if (s.type != C.NOTE) { + error(1, s, errs.must_note, nm) + continue + } j = curvoice.acc[s.notes[0].pit + 19] if (s.notes[0].acc || !j) continue court = 1 // defer break case 44: // cross-voice ties + if (s.type != C.NOTE) { + error(1, s, errs.must_note, nm) + continue + } do_ctie(nm, s, s.notes[0]) // (only one note for now) continue // default: // break } @@ -1128,13 +1047,15 @@ // case 5: // trill // case 7: // d_cresc error(1, s, "Cannot have !$1! on a head", nm) continue case 9: // head replacement + nt.invis = true + break case 32: // invisible nt.invis = true - break + continue case 10: // color nt.color = nm continue case 40: // stemless chord (abcm2ps behaviour) s.stemless = true @@ -1163,26 +1084,24 @@ de.x += dx } } /* -- adjust the symbol width -- */ -function deco_width(s) { +function deco_width(s, wlnt) { var dd, i, w, - wl = 0, + wl = wlnt, + wr = s.wr, a_dd = s.a_dd, nd = a_dd.length for (i = 0; i < nd; i++) { dd = a_dd[i] switch (dd.func) { case 1: /* slide */ - if (wl < 7) - wl = 7 - break case 2: /* arpeggio */ - if (wl < 14) - wl = 14 + if (wl < 12) + wl = 12 break case 3: switch (dd.glyph) { case "brth": case "lphr": @@ -1189,30 +1108,33 @@ case "mphr": case "sphr": if (s.wr < 20) s.wr = 20 break + default: + w = dd.wl + 2 + if (wl < w) + wl = w + break } // fall thru default: switch (dd.ty) { case '<': - w = dd.wl + dd.wr + 6 + w = wlnt + dd.wl + dd.wr + 6 if (wl < w) wl = w break case '>': - w = dd.wl + dd.wr + 8 + w = wr + dd.wl + dd.wr + 6 if (s.wr < w) s.wr = w break } break } } - if (wl && s.prev && s.prev.type == C.BAR) - wl -= 3 return wl } // compute the width of decorations in chord function deco_wch(nt) { @@ -1221,11 +1143,11 @@ n = nt.a_dd.length for (i = 0; i < n; i++) { dd = nt.a_dd[i] if (dd.ty == '<') { - w = dd.wl + dd.wr + 8 + w = dd.wl + dd.wr + 4 if (w > wl) wl = w } } return wl @@ -1277,12 +1199,12 @@ set_scale(s); st = de.st; if (!staff_tb[st].topbar) continue // invisible staff - x = de.x; - y = de.y + staff_tb[st].y + x = de.x + (dd.dx || 0) + y = de.y + staff_tb[st].y + (dd.dy || 0) // update the coordinates if head decoration if (de.m != undefined) { note = s.notes[de.m]; if (note.shhd) @@ -1326,18 +1248,20 @@ } } } // check if user JS decoration - uf = user[f] - if (uf && typeof(uf) == "function") { - uf(x, y, de) - continue + if (user.deco) { + uf = user.deco[f] + if (uf && typeof(uf) == "function") { + uf.call(self, x, y, de) + continue + } } // check if user PS definition - if (self.psdeco(f, x, y, de)) + if (self.psdeco(x, y, de)) continue anno_start(s, 'deco') // if (de.flags.grace) { // g_open(x, y, 0, .7, de.inv ? -.7 : 0); @@ -1355,21 +1279,15 @@ out_deco_val(x, y, f, de.val / stv_g.scale, de.defl) else out_deco_val(x, y, f, de.val, de.defl) if (de.cont) new_de.push(de.start) // to be continued next line - } else if (dd.str != undefined) { - str = dd.str - if (str[0] == '@') { - a = str.match(/^@([0-9.-]+),([0-9.-]+);?/); - x += Number(a[1]); - y += Number(a[2]); - str = str.replace(a[0], "") - } -// out_deco_str(x, y + de.dy, // - dd.h * .2, + } else if (dd.str != undefined // string + && !tgls[dd.glyph] + && !glyphs[dd.glyph]) { // with a class out_deco_str(x, y, // - dd.h * .2, - f, str) + de) } else if (de.lden) { out_deco_long(x, y, de) } else { xygl(x, y, f) } @@ -1404,11 +1322,11 @@ } } /* -- create the deco elements, and treat the near ones -- */ function create_deco(s) { - var dd, k, pos, de, x, y, v, + var dd, k, pos, de, x, y, up, nd = s.a_dd.length if (s.y == undefined) s.y = 0 // (no y in measure bars) @@ -1426,17 +1344,13 @@ pos = 0 break case 3: /* d_upstaff */ case 4: case 5: // after slurs - case 7: pos = s.pos.orn break - case 6: /* d_pf */ - pos = s.pos.vol - break - case 7: /* d_cresc */ + case 6: /* dynamic */ pos = s.pos.dyn break } switch (dd.ty) { // explicit position @@ -1450,11 +1364,11 @@ case '>': pos = (pos & 0x07) | C.SL_CLOSE if (dd.ty == '<') { x -= dd.wr + 8 if (s.notes[0].acc) - x -= 5.5 + x -= 8 } else { x += dd.wl + 8 } y = 3 * (s.notes[0].pit - 18) - (dd.h - dd.hd) / 2 @@ -1473,14 +1387,40 @@ dd: dd, st: s.st, ix: a_de.length, defl: {}, x: x, - y: y, - pos: pos -// dy: 0 + y: y + } + if (pos) + de.pos = pos + + up = 0 //false + if (dd.ty == '^') { + up = 1 //true + } else if (dd.ty == '_') { + ; + } else { + switch (dd.func) { + case 0: + if (s.multi) + up = s.multi > 0 + else + up = s.stem < 0 + break + case 3: + case 5: + up = up3(s, pos) + break + case 6: + case 7: + up = up6(s, pos) + break + } } + de.up = up + if (dd.name.indexOf("inverted") >= 0) de.inv = 1 if (s.type == C.BAR && !dd.ty) de.x -= s.wl / 2 - 2 a_de.push(de) @@ -1514,24 +1454,27 @@ st: s.st, m: m, ix: 0, defl: {}, x: x, - y: 3 * (note.pit - 18) + y: 3 * (note.pit - 18) - (dd.h - dd.hd) / 2 // dy: 0 } if (dd.ty) { // if explicit position if (dd.ty == '@') { de.x += dd.x de.y += dd.y } else { de.y -= (dd.h - dd.hd) / 2 // center - if (dd.ty == '<') + if (dd.ty == '<') { de.x -= dd.wr + 8 - else if (dd.ty == '>') - de.x += dd.wr + 8 + if (s.notes[m].acc) + x -= 8 + } else if (dd.ty == '>') { + de.x += dd.wl + 8 + } } } a_de.push(de) if (dd.dd_en) { @@ -1638,11 +1581,11 @@ ix: a_de.length - 1, // x: s.x - s.wl - 4, y: s.y, ldst: true } - de.x = de.s.x + de.x = de.s.x + de.s.wr if (de2.m != undefined) de.m = de2.m; a_de.push(de); de2.start = de } @@ -1703,11 +1646,11 @@ // - chord symbols // - repeat brackets /* (the staves are not yet defined) */ /* (unscaled delayed output) */ function draw_deco_staff() { - var s, p_voice, y, i, v, de, dd, + var s, p_voice, y, i, v, de, dd, w, minmax = new Array(nstaff + 1), nd = a_de.length /* draw the repeat brackets */ function draw_repbra(p_voice) { @@ -1733,17 +1676,20 @@ break s = s.next if (s.rbstop) break } - y2 = y_get(p_voice.st, true, s1.x, s.x - s1.x) + 2 + x = s1.x + if (s1.xsh) // volta shift + x += s1.xsh + y2 = y_get(p_voice.st, true, x, s.x - x) + 2 if (y < y2) y = y2 // have room for the vertical lines and the repeat numbers if (s1.rbstart == 2) { - y2 = y_get(p_voice.st, true, s1.x, 3) + 10 + y2 = y_get(p_voice.st, true, x, 3) + 10 if (y < y2) y = y2 } if (s.rbstop == 2) { y2 = y_get(p_voice.st, true, s.x - 3, 3) + 10 @@ -1750,11 +1696,11 @@ if (y < y2) y = y2 } if (s1.text) { wh = strwh(s1.text); - y2 = y_get(p_voice.st, true, s1.x + 4, wh[0]) + + y2 = y_get(p_voice.st, true, x + 4, wh[0]) + wh[1] if (y < y2) y = y2 } if (s.rbstart) @@ -1779,10 +1725,12 @@ break } if (s1 == s) break x = s1.x + if (s1.xsh) // volta shift + x += s1.xsh if (cfmt.measurenb > 0 & s.bar_num && s.bar_num % cfmt.measurenb) x += 6 if (s.type != C.BAR) { w = s.rbstop ? 0 : s.x - realwidth + 4 @@ -1825,11 +1773,11 @@ output += 'v10'; output += '"/>\n'; y_set(s1.st, true, x, w, y + 2) if (s.rbstart) - s = s.prev.prev + s = s.prev } } // draw_repbra() /* create the decorations tied to the staves */ for (i = 0; i <= nstaff; i++) @@ -1841,15 +1789,19 @@ de = a_de[i]; dd = de.dd if (!dd) // if error continue if (!f_staff[dd.func] /* if not tied to the staff */ - || de.m != undefined) // or head decoration + || de.m != undefined // or head decoration + || dd.ty == '<' || dd.ty == '>' || dd.ty == '@') continue + f_staff[dd.func](de) - if (dd.dd_en) // if start + if (dd.func != 6 + || dd.dd_en) // if start continue + if ((de.pos & C.SL_ALI_MSK) == C.SL_ALIGN || ((de.pos & C.SL_ALI_MSK) == 0 && de.s.fmt.dynalign > 0)) { // if align if (de.up) { if (de.y > minmax[de.st].ymax) @@ -1859,23 +1811,43 @@ minmax[de.st].ymin = de.y } } } - /* and, if wanted, set them at a same vertical offset */ + // set the same vertical offset of the dynamic marks for (i = 0; i < nd; i++) { de = a_de[i]; dd = de.dd - if (!dd) // if error + if (!dd) // if error + continue + + // if @x,y offsets, update the top and bottom of the staff + if (dd.ty == '@') { + var y2 + + y = de.y + if (y > 0) { + y2 = y + dd.h + 2 + if (y2 > staff_tb[de.st].ann_top) + staff_tb[de.st].ann_top = y2 + } else { + y2 = y - dd.hd - 2 + if (y2 < staff_tb[de.st].ann_bot) + staff_tb[de.st].ann_bot = y2 + + } continue - if (dd.dd_en // if start - || !f_staff[dd.func]) + } + if (dd.func != 6 + || dd.ty == '<' || dd.ty == '>' + || dd.dd_en) // if start continue - if (dd.func == 6 - &&((de.pos & C.SL_ALI_MSK) == C.SL_ALIGN + + w = de.val || (dd.wl + dd.wr) + if ((de.pos & C.SL_ALI_MSK) == C.SL_ALIGN || ((de.pos & C.SL_ALI_MSK) == 0 - && de.s.fmt.dynalign > 0))) { // if align + && de.s.fmt.dynalign > 0)) { // if align if (de.up) y = minmax[de.st].ymax else y = minmax[de.st].ymin; de.y = y @@ -1882,11 +1854,28 @@ } else { y = de.y } if (de.up) y += dd.h; - y_set(de.st, de.up, de.x, de.val, y) + else + y -= dd.hd + y_set(de.st, de.up, de.x, w, y) + } + + // second pass for pedal (under the staff) + for (i = 0; i < nd; i++) { + de = a_de[i] + dd = de.dd + if (!dd) // if error + continue + if (dd.dd_en // if start + || dd.name.slice(0, 3) != "ped") + continue + w = de.val || 10 + de.y = y_get(de.st, 0, de.x, w) + - (dd.dd_st && cfmt.pedline ? 10 : dd.h) + y_set(de.st, 0, de.x, w, de.y) // (no descent) } draw_all_chsy() // draw all chord symbols /* draw the repeat brackets */ @@ -1950,11 +1939,13 @@ w = w0 if (bar_num >= 10) w *= bar_num >= 100 ? 3 : 2 if (gene.curfont.pad) w += gene.curfont.pad * 2 - x = s.x - s.wl - w + x = (s.prev + ? s.prev.x + s.prev.wr / 2 + : s.x - s.wl) - w y = y_get(st, true, x, w) + 5 if (y < staff_tb[st].topbar + 6) y = staff_tb[st].topbar + 6; y += gene.curfont.pad xy_str(x, y - gene.curfont.size * .2, bar_num.toString()) @@ -2020,70 +2011,32 @@ if (font_size) param_set_font("measurefont", "* " + font_size.toString()); } /* -- draw the parts and the tempo information -- */ -/* (the staves are being defined) */ -function draw_partempo(st, top) { - var s, s2, some_part, some_tempo, h, w, y, - dy = 0, /* put the tempo indication at top */ - ht = 0 +// (unscaled delayed output) +function draw_partempo() { + var s, s2, some_part, some_tempo, h, w, y, st, + sy = cur_sy + + // search the top staff + for (st = 0; st <= nstaff; st++) { + if (sy.st_print[st]) + break + } + if (st > nstaff) + return // no visible staff + set_dscale(st, 1) // no scale /* get the minimal y offset */ - var ymin = staff_tb[st].topbar + 8, + var ymin = staff_tb[st].topbar + 2, dosh = 0, shift = 1, - x = -100 // (must be negative for %%soloffs) - - for (s = tsfirst; s; s = s.ts_next) { - if (s.type != C.TEMPO || s.invis) - continue - if (!some_tempo) - some_tempo = s; - w = s.tempo_wh[0] - if (s.time == 0 && s.x > 40) // at start of tune and no %%soloffs, - s.x = 40; // shift the tempo over the key signature - y = y_get(st, true, s.x - 16, w) - if (y > ymin) - ymin = y - if (x >= s.x - 16 && !(dosh & (shift >> 1))) - dosh |= shift; - shift <<= 1; - x = s.x - 16 + w - } - if (some_tempo) { - set_sscale(-1); - set_font("tempo"); - ht = gene.curfont.size + 8; - y = 2 - ht; - h = y - ht - if (dosh != 0) - ht *= 2 - if (top < ymin + ht) - dy = ymin + ht - top - - /* draw the tempo indications */ - for (s = some_tempo; s; s = s.ts_next) { - if (s.type != C.TEMPO - || s.invis) // (displayed by %%titleformat) - continue - if (user.anno_start || user.anno_stop) { - s.wl = 16; - s.wr = 30; - s.ymn = (dosh & 1) ? h : y; - s.ymx = s.ymn + 14; - anno_start(s) - } - writempo(s, s.x - 16, (dosh & 1) ? h : y); - anno_stop(s); - dosh >>= 1 - } - } - - /* then, put the parts */ -/*fixme: should reduce vertical space if parts don't overlap tempo...*/ - ymin = staff_tb[st].topbar + 6 + x = -100, // (must be negative for %%soloffs) + yn = 0 // y min when x < 0 + + // output the parts for (s = tsfirst; s; s = s.ts_next) { s2 = s.part if (!s2 || s2.invis) continue if (!some_part) { @@ -2099,29 +2052,79 @@ if (ymin < y) ymin = y } if (some_part) { set_sscale(-1) - ht += h - if (top + dy < ymin + ht) - dy = ymin + ht - top - + 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] if (user.anno_start || user.anno_stop) { - s2.type = C.PART - s2.p_v = s.p_v - s2.v = s.v - s2.st = s2.wl = 0 - s2.wr = strwh(s2.text)[0] - s2.ymn = -ht + s2.wl = 0 + s2.wr = w + s2.ymn = ymin s2.ymx = s2.ymn + h anno_start(s2) } - xy_str(s2.x, 2 - ht + gene.curfont.size * .22, s2.text) + 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) + if (s2.x < 0) + yn = ymin + 2 + h anno_stop(s2) } } - return dy /= staff_tb[0].staffscale + + // output the tempos + ymin = staff_tb[st].topbar + 6 + for (s = tsfirst; s; s = s.ts_next) { + if (s.type != C.TEMPO || s.invis) + continue + if (!some_tempo) + some_tempo = s + w = s.tempo_wh[0] +// if (s.time == 0 && s.x > 40) // at start of tune and no %%soloffs, +// s.x = 40 // shift the tempo over the key signature + y = y_get(st, true, s.x - 16, w) + if (s.x - 16 < 0) + y = yn + if (y > ymin) + ymin = y + if (x >= s.x - 16 && !(dosh & (shift >> 1))) + dosh |= shift + shift <<= 1 + x = s.x - 16 + w + } + if (some_tempo) { + set_sscale(-1) + set_font("tempo") + h = gene.curfont.size + ymin += 2 + ymin *= staff_tb[st].staffscale + + /* draw the tempo indications */ + for (s = some_tempo; s; s = s.ts_next) { + if (s.type != C.TEMPO + || s.invis) // (displayed by %%titleformat) + continue + w = s.tempo_wh[0] + y = ymin + if (dosh & 1) + y += h + if (user.anno_start || user.anno_stop) { + s.wl = 16 +// s.wr = 30 + s.wr = w - 16 + s.ymn = y + 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) + dosh >>= 1 + } + } } Index: core/draw.js ================================================================== --- core/draw.js +++ core/draw.js @@ -96,11 +96,11 @@ ] // (possible hook) Abc.prototype.calculate_beam = function(bm, s1) { var s, s2, g, notes, nflags, st, v, two_staves, two_dir, - x, y, ys, a, b, stem_err, max_stem_err, + n, x, y, ys, a, b, stem_err, max_stem_err, p_min, p_max, s_closest, stem_xoff, scale, visible, dy if (!s1.beam_st) { /* beam from previous music line */ @@ -195,18 +195,18 @@ continue if ((scale = s.p_v.scale) == 1) scale = staff_tb[s.st].staffscale if (s.stem >= 0) { x = stem_xoff + s.notes[0].shhd - if (s.notes[s.nhd].pit > p_max) { - p_max = s.notes[s.nhd].pit; + if (s.notes[s.nhd].midi > p_max) { + p_max = s.notes[s.nhd].midi s_closest = s } } else { x = -stem_xoff + s.notes[s.nhd].shhd - if (s.notes[0].pit < p_min) { - p_min = s.notes[0].pit; + if (s.notes[0].midi < p_min) { + p_min = s.notes[0].midi s_closest = s } } s.xs = s.x + x * scale; if (s == s2) @@ -222,37 +222,57 @@ && notes >= 3 && s_closest != s1 && s_closest != s2) a = 0 y = s1.ys + staff_tb[st].y - if (a == undefined) + if (a == undefined) { + if (two_dir + && s1.stem != s2.stem + && s1.st == s2.st) { // if inverted stems, + y -= 5 * s1.stem // remove the beam depth + s2.ys -= 5 * s2.stem + } a = (s2.ys + staff_tb[s2.st].y - y) / (s2.xs - s1.xs) + } if (a != 0) { a = s1.fmt.beamslope * a / (s1.fmt.beamslope + Math.abs(a)) // max steepness for beam if (a > -.04 && a < .04) a = 0 // slope too low } - // pivot around the middle of the beam + // center the main beam b = (y + s2.ys + staff_tb[s2.st].y) / 2 - a * (s2.xs + s1.xs) / 2 -/*fixme: have a look again*/ /* have room for the symbols in the staff */ max_stem_err = 0; /* check stem lengths */ + + // when 2 directions, check if all beams are on the same side of the main beam s = s1 - if (two_dir) { /* 2 directions */ -/*fixme: more to do*/ - ys = ((s1.grace ? 3.5 : BEAM_SHIFT) * (nflags - 1) + - BEAM_DEPTH) * .5 - if (s1.nflags == s2.nflags) - ; - else if (s1.stem != s2.stem && s1.nflags < s2.nflags) - b += ys * s2.stem - else - b += ys * s1.stem + if (two_dir) { + n = 1 + while (1) { + if (s.stem != s1.stem + && (s.nflags == 1 + || s.beam_br1 || s.beam_br2)) { + n = 0 + break + } + if (s == s2) + break + s = s.next + } + if (n) // same side + n = (s1.nflags + s2.nflags) + * (s1.nflags >= s2.nflags ? s1.stem : s2.stem) + / 4 + else // different sides + n = -(s1.nflags * s1.stem + s2.nflags * s2.stem) + / 2 + b += ((s1.grace ? 3.5 : BEAM_SHIFT) * n + + BEAM_DEPTH * s1.stem) / 2 } else if (!s1.grace) { /* normal notes */ var beam_h = BEAM_DEPTH + BEAM_SHIFT * (nflags - 1) //--fixme: added for abc2svg while (s.ts_prev && s.ts_prev.type == C.NOTE @@ -337,12 +357,11 @@ } if (max_stem_err > 0) /* shift beam if stems too short */ b += s1.stem * max_stem_err - /* have room for the gracenotes, bars and clefs */ -/*fixme: test*/ + // have room for the gracenotes and clefs if (!two_staves && !two_dir) for (s = s1.next; ; s = s.next) { switch (s.type) { case C.REST: /* cannot move rests in multi-voices */ if (!s.multi) @@ -352,11 +371,11 @@ || (g.type != C.NOTE && g.type != C.REST)) break //fixme:too much vertical shift if some space above the note //fixme:this does not fix rest under beam in second voice (ts_prev) /*fall thru*/ - case C.BAR: +// case C.BAR: if (s.invis) break /*fall thru*/ case C.CLEF: y = a * s.x + b @@ -458,12 +477,13 @@ } /* -- draw the beams for one word -- */ /* (the staves are defined) */ function draw_beams(bm) { - var s, i, beam_dir, shift, bshift, bstub, bh, da, bd, + var s, i, beam_dir, shift, bshift, bstub, bh, da, bsh, k, k1, k2, x1, + osh = 0, // shift other side s1 = bm.s1, s2 = bm.s2 /* -- draw a single beam -- */ function draw_beam(x1, x2, dy, h, bm, @@ -518,11 +538,11 @@ bh /= stv_g.scale /*fixme: quick hack for stubs at end of beam and different stem directions*/ beam_dir = s1.stem if (s1.stem != s2.stem - && s1.nflags < s2.nflags) + && s1.nflags > s2.nflags) beam_dir = s2.stem if (beam_dir < 0) bh = -bh; /* make first beam over whole word and adjust the stem lengths */ @@ -593,11 +613,11 @@ } k2 = s while (k2.type != C.NOTE) k2 = k2.prev; x1 = k1.xs - bd = beam_dir + bsh = shift * beam_dir if (k1 == k2) { if (k1 == s1) { x1 += bstub } else if (k1 == s2) { x1 -= bstub @@ -623,17 +643,31 @@ else x1 -= bstub } } if (k1.stem != beam_dir) { - bd = k1.stem + osh -= bshift + bsh = osh * beam_dir k1.ys = bm.a * k1.xs + bm.b - staff_tb[k1.st].y - bh } + } else if (k1.stem == k2.stem && k1.stem != beam_dir) { + + // inverted stems: put the beam on the other side + osh -= bshift + bsh = osh * beam_dir + for (s = k1; ; s = s.next) { + if (s.type == C.NOTE) + s.ys = bm.a * s.xs + bm.b + - staff_tb[s.st].y + - bh + if (s == k2) + break + } } draw_beam(x1, k2.xs, - shift * bd, + bsh, bh, bm, i) if (s == s2) break } } @@ -1165,10 +1199,38 @@ function draw_mrest(s) { var x1, x2, prev, p_st = staff_tb[s.st], y = p_st.y + (p_st.topbar + p_st.botbar) / 2, p = s.nmes.toString() + + // output an old multimeasure rest + function omrest() { + var x = s.x, + y = p_st.y + 12, + n = s.nmes, + k = n >> 2 // number of rests + + if (n & 3) { + k++ + if (n & 3 == 3) + k++ + } +//dx = 6 + x -= 3 * (k - 1) + while (n >= 4) { + xygl(x, y, "r00") + n -= 4 + x += 6 + } + if (n >= 2) { + xygl(x, y, "r0") + n -= 2 + x += 6 + } + if (n) + xygl(x + 2, y, "r1") + } // omrest() if (!s.next) { error(1, s, "Lack of bar after multi-measure rest") return } @@ -1183,14 +1245,23 @@ x1 = prev.x + 20 x2 = s.next.x - 20 s.x = (x1 + x2) / 2 anno_start(s) - out_XYAB('\n' + if (!cfmt.oldmrest || s.nmes > cfmt.oldmrest) { + out_XYAB('\n' + } else { + omrest() // old multirest + } + if (s.tacet) + out_XYAB('A\n', + s.x, y + 18, s.tacet) + else out_XYAB('A\n', s.x, y + 22, m_gl(p)) anno_a.push(s) } // draw_mrest() @@ -1746,44 +1817,129 @@ // draw a slur between two chords / notes /* (the staves are not yet defined) */ /* (delayed output) */ /* (not a pretty routine, this) */ function draw_slur(path, // list of symbols under the slur - sl) { // slur variables: type, end symbol, note + sl, // slur variables: type, end symbol, note + recurr) { // recurrent call when slur on two staves var i, - k, g, x1, y1, x2, y2, height, addy, + k, g, x1, y1, x2, y2, height, addy, s_st2, a, y, z, h, dx, dy, ty = sl.ty, dir = (ty & 0x07) == C.SL_ABOVE ? 1 : -1, n = path.length, i1 = 0, i2 = n - 1, - not1 = sl.nts, // if the slur start on a note + not1 = sl.nts, // if the slur starts on a note k1 = path[0], - k2 = path[i2] - -/*fixme: if two staves, may have upper or lower slur*/ - - var nn = 1, - upstaff = k1.st, - two_staves = false + k2 = path[i2], + nn = 1 set_dscale(k1.st) for (i = 1; i < n; i++) { k = path[i] if (k.type == C.NOTE || k.type == C.REST) { nn++ - if (k.st != upstaff) { - two_staves = true - if (k.st < upstaff) - upstaff = k.st - } + if (k.st != k1.st + && !s_st2) + s_st2 = k } } -/*fixme: KO when two staves*/ -if (two_staves) error(2, k1, "*** multi-staves slurs not treated yet"); + + // if slur on 2 staves, define it, but don't output it now + // this will be done in draw_sl2() + if (s_st2 && !recurr) { // if not 2nd call to draw_slur() + if (!gene.a_sl) + gene.a_sl = [] + + // replace the symbols of the other staff + // by symbols in the current staff but with updated y offsets + h = 24 + k1.fmt.sysstaffsep // delta y + if (s_st2.st > k1.st) + h = -h + for (i = 0; i < n; i++) { + k = path[i] + if (k.st == k1.st) { + if (k.dur) + a = k // (used for types // and \\) + continue + } + k = clone(k) + if (path[i] == s_st2) + s_st2 = k + path[i] = k + if (k.dur) { + k.notes = clone(k.notes) + k.notes[0] = clone(k.notes[0]) + if (sl.ty & C.SL_CENTER) { + if (k.st != a.st) { + sl.ty = (sl.ty & ~0x07) + | (a.st < k.st + ? C.SL_BELOW + : C.SL_ABOVE) + z = k1.ymn + h = k2.ymx + if (k.st < a.st) { + for (i1 = 1; i1 < i; i1++) { + a = path[i1] + if (a.ymn < z) + z = a.ymn + } + for (i1 = i; i1 < i2; i1++) { + a = path[i1] + if (a.ymx > h) + h = a.ymx + } + } else { + for (i1 = 1; i1 < i; i1++) { + a = path[i1] + if (a.ymx > h) + h = a.ymx + } + for (i1 = i; i1 < i2; i1++) { + a = path[i1] + if (a.ymn < z) + z = a.ymn + } + } + h += z + a = k + } + k.y = h - k.y + k.notes[0].pit = (k.y / 3 | 0) + 18 + k.ys = h - k.ys + y = k.ymx + k.ymx = h - k.ymn + k.ymn = h - y + k.stem = -k.stem + } else { + k.notes[0].pit += h / 3 | 0 + k.ys += h + k.y += h + k.ymx += h + k.ymn += h + } + } +// k.st = k1.st // keep the staff number for draw_sl2() + } + + ty = k1.st > s_st2.st ? '/' : '\\' + if (sl.ty & C.SL_CENTER) + ty = ty + ty // type = // or \\ + else if (k1.st == k2.st) + ty = ty == '/' ? '/\\' : '\\/' // type = /\ or \/ + else + ty += dir > 0 ? '+' : '-' // type = .+ or .- + + var savout = output + output = "" + draw_slur(path, sl, 1 /*true*/) + gene.a_sl.push([k1, s_st2, ty, output]) + output = savout + return + } /* fix endpoints */ x1 = k1.x if (k1.notes && k1.notes[0].shhd) x1 += k1.notes[0].shhd; @@ -1960,12 +2116,10 @@ if (k1 != k2 && k1.v == k2.v) { addy = y1 - a * x1 for (i = 1; i < i2; i++) { k = path[i] - if (k.st != upstaff) - continue switch (k.type) { case C.NOTE: case C.REST: if (dir > 0) { y = 3 * (k.notes[k.nhd].pit - 18) + 6 @@ -2052,32 +2206,32 @@ addy += 4 * Math.sqrt(height) - 2 else addy -= 4 * Math.sqrt(-height) - 2 for (i = 0; i < i2; i++) { k = path[i] - if (k.st != upstaff || k.type == C.BAR) + if (k.st != k1.st || k.type == C.BAR) continue y = a * k.x + addy if (k.ymx < y) 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) { dx = x2 if (sl.nte) dx -= 5; - if (i) - y -= height / 3 } else { - dx = path[i + 1].x + dx = k.x + k.wr } if (i != 0) x1 = k.x - else + if (!i || i == i2) y -= height / 3 - dx -= x1; - y_set(upstaff, dir > 0, x1, dx, y) + dx -= x1 - k.wl + y_set(k1.st, dir > 0, x1 - k.wl, dx, y) } } /* -- draw the slurs between 2 symbols --*/ function draw_slurs(s, last) { @@ -2094,28 +2248,11 @@ return // will be drawn next time // handle slurs without start or end switch (sl.loc) { case 'i': // no start - s3 = s.ts_prev - for (s = s3; s; s = s.ts_prev) { - if (s.dur) { - if (s.v == s2.v) { - s3 = s - break - } - if (s.st == s2.st) { - s3 = s - continue - } else if (!s3) { - s3 = s - } - } else if (!s3) { - s3 = s - } - } - s = s3 + s = prev_scut(s) break case 'o': // no end for (s3 = s; s3.ts_next; s3 = s3.ts_next) ; s2 = s3 @@ -2132,21 +2269,31 @@ break } // if the slur continues on the next music line, // stop it at the end of the current line - if (tsnext && s2.time >= tsnext.time) { - if (s2.grace && s2.time == tsnext.time) { + if (s.p_v.s_next && s2.time >= tsnext.time) { + if (s2.time == tsnext.time) { + if (s2.grace) { for (s3 = tsnext; s3 && s3.time == s2.time; s3 = s3.ts_next) { - if (s3.type == C.GRACE) -//fixme: check if s2 in the grace notes - break - } - if (s3.type == C.GRACE) - s3 = null - } - if (!s2.grace || s2.time == tsnext.time || !s3) { + if (s3.type == C.GRACE) { + s3 = null + break + } + } + } else { + for (s3 = tsnext; s3.time == s2.time; s3 = s3.ts_next) { + if (s3 == s2) { + s3 = null // end of slur in next line + break + } + } + } + } else { + s3 = null + } + if (!s3) { s.p_v.sls.push(sl); // continuation on next line s2 = s.p_v.s_next.prev // one voice while (s2.next) s2 = s2.next; // search the ending bar sl = Object.create(sl); // new slur @@ -2195,12 +2342,11 @@ s = s.ts_next } if (!s2.grace) { // if end on a normal note while (s) { - if (s.v == v - && !s.invis) + if (s.v == v) path.push(s) if (s == s2) break s = s.ts_next } @@ -2277,20 +2423,19 @@ /* (the staves are not yet defined) */ /* (delayed output) */ /* See http://moinejf.free.fr/abcm2ps-doc/tuplets.html * for the value of 'tp.f' */ function draw_tuplet(s1) { - var s2, s3, g, upstaff, nb_only, + var s2, s3, g, stu, std, nb_only, x1, x2, y1, y2, xm, ym, a, s0, yy, yx, dy, a, dir, r, tp = s1.tp.shift() // tuplet parameters if (!s1.tp.length) delete s1.tp // last tuplet // treat the slurs and the nested tuplets - upstaff = s1.st - set_dscale(s1.st) + stu = std = s1.st for (s2 = s1; s2; s2 = s2.next) { switch (s2.type) { case C.GRACE: if (!s2.sl1) continue @@ -2305,12 +2450,16 @@ case C.REST: break } if (s2.sls) draw_slurs(s2) - if (s2.st < upstaff) - upstaff = s2.st + if (s2.st < stu) { + std = stu + stu = s2.st + } else if (s2.st > std) { + std = s2.st + } if (s2.tp) draw_tuplet(s2) if (s2.tpe) break } @@ -2331,10 +2480,11 @@ s3 = s1 while (s3 && !s3.stem) // (may have tuplets of rests!) s3 = s3.next dir = (s3 && s3.stem < 0) ? C.SL_BELOW : C.SL_ABOVE } + set_dscale(dir == C.SL_ABOVE ? stu : std) if (s1 == s2 // tuplet with 1 note (!) || tp.f[1] == 2) { // what == nothing nb_only = true } else if (tp.f[1] == 1) { /* 'what' == slur */ @@ -2395,45 +2545,46 @@ if (tp.f[2] == 1) /* if 'which' == none */ return set_font("tuplet") xm = (s2.x + s1.x) / 2 if (dir == C.SL_ABOVE) // 8 = width around the middle - ym = y_get(upstaff, 1, xm - 4, 8) + ym = y_get(stu, 1, xm - 4, 8) else - ym = y_get(upstaff, 0, xm - 4, 8) - + ym = y_get(std, 0, xm - 4, 8) - gene.curfont.size if (s1.stem * s2.stem > 0) { if (s1.stem > 0) - xm += 1.5 + xm += 4 else - xm -= 1.5 + xm -= 4 } + yy = ym + gene.curfont.size * .22 if (tp.f[2] == 0) // if 'which' == number - xy_str(xm, ym, tp.p.toString(), 'c') + xy_str(xm, yy, tp.p.toString(), 'c') else - xy_str(xm, ym, tp.p+':'+tp.q, 'c') + xy_str(xm, yy, tp.p + ':' + tp.q, 'c') for (s3 = s1; ; s3 = s3.next) { if (s3.x >= xm) break } if (dir == C.SL_ABOVE) { ym += gene.curfont.size if (s3.ymx < ym) s3.ymx = ym; - y_set(upstaff, true, xm - 3, 6, ym) + y_set(stu, 1, xm - 3, 6, ym) } else { if (s3.ymn > ym) s3.ymn = ym; - y_set(upstaff, false, xm - 3, 6, ym) + y_set(std, 0, xm - 3, 6, ym) } return } - // here, what is square bracket + // here, 'what' is square bracket /*fixme: two staves not treated*/ /*fixme: to optimize*/ // first, get the x offsets @@ -2455,42 +2606,34 @@ } else { x2 = s2.x + 4 r = s2.stem >= 0 ? 0 : s2.nhd if (s2.notes[r].shhd > 0) x2 += s2.notes[r].shhd - if (s2.st == upstaff + if (s2.st == stu && s2.stem > 0) x2 += 3.5 } // above if (dir == C.SL_ABOVE) { - - /* sole or upper voice: the bracket is above the staff */ - if (s1.st == s2.st) { - y1 = y2 = staff_tb[upstaff].topbar + 2 - } else { - y1 = s1.ymx; - y2 = s2.ymx - } - - if (s1.st == upstaff) { - for (s3 = s1; !s3.dur; s3 = s3.next) - ; - ym = y_get(upstaff, 1, s3.x - 4, 8) - if (ym > y1) - y1 = ym + if (s1.st >= s2.st) { if (s1.stem > 0) x1 += 3 - } - - if (s2.st == upstaff) { - for (s3 = s2; !s3.dur; s3 = s3.prev) - ; - ym = y_get(upstaff, 1, s3.x - 4, 8) - if (ym > y2) - y2 = ym + ym = y_get(s1.st, 1, x1 - 4, 8) + y1 = ym > staff_tb[s1.st].topbar + 2 + ? ym + : staff_tb[s1.st].topbar + 2 + } else { + y1 = staff_tb[s1.st].topbar + 2 + } + if (s2.st >= s1.st) { + ym = y_get(s2.st, 1, x2 - 4, 8) + y2 = ym > staff_tb[s2.st].topbar + 2 + ? ym + : staff_tb[s2.st].topbar + 2 + } else { + y2 = staff_tb[s2.st].topbar + 2 } xm = .5 * (x1 + x2); ym = .5 * (y1 + y2); @@ -2513,17 +2656,17 @@ /* shift up bracket if needed */ dy = 0 for (s3 = s1; ; s3 = s3.next) { if (!s3.dur /* not a note or a rest */ - || s3.st != upstaff) { + || s3.st != stu) { if (s3 == s2) break continue } yy = ym + (s3.x - xm) * a; - yx = y_get(upstaff, 1, s3.x - 4, 8) + 2 + yx = y_get(stu, 1, s3.x - 4, 8) + 2 if (yx - yy > dy) dy = yx - yy if (s3 == s2) break } @@ -2533,39 +2676,39 @@ y2 = ym + a * (x2 - xm); /* shift the slurs / decorations */ ym += 6 for (s3 = s1; ; s3 = s3.next) { - if (s3.st == upstaff) { + if (s3.st == stu) { yy = ym + (s3.x - xm) * a if (s3.ymx < yy) s3.ymx = yy - y_set(upstaff, true, s3.x - 3, 6, yy) + y_set(stu, 1, s3.x - 3, 6, yy) } if (s3 == s2) break } // below } else { /* lower voice of the staff: the bracket is below the staff */ -/*fixme: think to all of that again..*/ - if (s1.stem < 0) - x1 -= 2 - - if (s1.st == upstaff) { - for (s3 = s1; !s3.dur; s3 = s3.next) - ; - y1 = y_get(upstaff, 0, s3.x - 4, 8) - } else { - y1 = 0 - } - if (s2.st == upstaff) { - for (s3 = s2; !s3.dur; s3 = s3.prev) - ; - y2 = y_get(upstaff, 0, s3.x - 4, 8) - } else { - y2 = 0 + if (s1.st <= s2.st) { + ym = y_get(s1.st, 0, x1 - 4, 8) + y1 = ym < -2 + ? ym + : -2 + } else { + y1 = -2 + } + if (s2.st <= s1.st) { + if (s2.stem < 0) + x2 -= 3 + ym = y_get(s2.st, 0, x2 - 4, 8) + y2 = ym < -2 + ? ym + : -2 + } else { + y2 = -2 } xm = .5 * (x1 + x2); ym = .5 * (y1 + y2); @@ -2591,17 +2734,17 @@ /* shift down the bracket if needed */ dy = 0 for (s3 = s1; ; s3 = s3.next) { if (!s3.dur /* not a note nor a rest */ - || s3.st != upstaff) { + || s3.st != std) { if (s3 == s2) break continue } yy = ym + (s3.x - xm) * a; - yx = y_get(upstaff, 0, s3.x - 4, 8) + yx = y_get(std, 0, s3.x - 4, 8) if (yx - yy < dy) dy = yx - yy if (s3 == s2) break } @@ -2611,15 +2754,15 @@ y2 = ym + a * (x2 - xm); /* shift the slurs / decorations */ ym -= 2 for (s3 = s1; ; s3 = s3.next) { - if (s3.st == upstaff) { + if (s3.st == std) { yy = ym + (s3.x - xm) * a if (s3.ymn > yy) s3.ymn = yy; - y_set(upstaff, false, s3.x - 3, 6, yy) + y_set(std, 0, s3.x - 3, 6, yy) } if (s3 == s2) break } } /* lower voice */ @@ -2630,13 +2773,13 @@ } out_tubrn(x1, y1, x2 - x1, y2 - y1, dir == C.SL_ABOVE, tp.f[2] == 0 ? tp.p.toString() : tp.p + ':' + tp.q); if (dir == C.SL_ABOVE) - y_set(upstaff, true, xm - 3, 6, yy + 2) + y_set(stu, 1, xm - 3, 6, yy + 2) else - y_set(upstaff, false, xm - 3, 6, yy) + y_set(std, 0, xm - 3, 6, yy) } // -- draw a ties -- function draw_tie(not1, not2, job) { // -: start and end @@ -2815,10 +2958,11 @@ * - measure numbers * - lyrics * - staff decorations * - chord symbols * - repeat brackets + * - parts and tempos * The buffer output is delayed until the definition of the staff system */ function draw_sym_near() { var p_voice, p_st, s, v, st, y, g, w, i, st, dx, top, bot, ymn, output_sav = output; @@ -3040,20 +3184,26 @@ } } draw_deco_staff() + draw_partempo() // draw the parts and tempo indications if any + set_dscale(-1); output = output_sav } /* -- draw the name/subname of the voices -- */ function draw_vname(indent, stl) { var p_voice, n, st, v, a_p, p, y, h, h2, staff_d = [] - for (st = stl.length; st >= 0; st--) { + if (!gene.vnt) + return + +// for (st = stl.length; st >= 0; st--) { + for (st = stl.length; --st >= 0; ) { if (stl[st]) break } if (st < 0) return @@ -3105,14 +3255,16 @@ } } // -- set the y offset of the staves and return the height of the whole system -- function set_staff() { - var i, st, prev_staff, v, + var i, st, prev_staff, v, fmt, s, y, staffsep, dy, maxsep, mbot, val, p_voice, p_staff, - fmt = tsfirst.fmt, sy = cur_sy + + // the last values of {,max}{,sys}staffsep are in the last format + fmt = tsnext ? tsnext.fmt : cfmt /* set the scale of the voices */ for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] if (p_voice.scale != 1) @@ -3124,29 +3276,23 @@ for (st = 0; st <= nstaff; st++) { if (gene.st_print[st]) break } y = 0 - if (st > nstaff) { - st--; /* one staff, empty */ - p_staff = staff_tb[st] - } -// else { + if (st > nstaff) + return y + p_staff = staff_tb[st] for (i = 0; i < YSTEP; i++) { val = p_staff.top[i] if (y < val) y = val } -// } - - /* draw the parts and tempo indications if any */ - y += draw_partempo(st, y) /* set the vertical offset of the 1st staff */ y *= p_staff.staffscale; - staffsep = fmt.staffsep * .5 + + staffsep = tsfirst.fmt.staffsep / 2 + p_staff.topbar * p_staff.staffscale if (y < staffsep) y = staffsep if (y < p_staff.ann_top) // absolute annotation y = p_staff.ann_top; @@ -3161,12 +3307,12 @@ var sy_staff_prev = sy.staves[prev_staff] for (st++; st <= nstaff; st++) { if (!gene.st_print[st]) continue p_staff = staff_tb[st] - staffsep = sy_staff_prev.sep || cfmt.sysstaffsep; - maxsep = sy_staff_prev.maxsep || cfmt.maxsysstaffsep; + staffsep = sy_staff_prev.sep || fmt.sysstaffsep; + maxsep = sy_staff_prev.maxsep || fmt.maxsysstaffsep; dy = 0 if (p_staff.staffscale == staff_tb[prev_staff].staffscale) { for (i = 0; i < YSTEP; i++) { val = p_staff.top[i] - @@ -3291,10 +3437,11 @@ w = x2 - x1; set_sscale(-1) // check if default staff if (cache && cache.st_l == stafflines + && staff_tb[st].staffscale == 1 && cache.st_w == (w | 0)) { xygl(x1, staff_tb[st].y, 'stdef' + cfmt.fullsvg) return } for (i = 0; i < l; i++, y -= il) { @@ -3323,11 +3470,12 @@ } ln += '"/>' } y = staff_tb[st].y if (!cache - && w > get_lwidth() - 10) { + && w > get_lwidth() - 10 + && staff_tb[st].staffscale == 1) { cache = { st_l: stafflines, st_w: w | 0 } i = 'stdef' + cfmt.fullsvg; @@ -3501,16 +3649,32 @@ default: set_hl(p_st, j, x, -7, 7) break } } // hl_rest() + + // return the left x offset of a new staff + // s is the %%staves + function st1(st, s) { + var tim = s.time + + do { // search a voice of this staff + s = s.ts_next + } while (s.st != st) + while (s.prev // search the first symbol of this voice + && s.prev.time >= tim) + s = s.prev + if (s.bar_type) + return s.x + return s.x - s.wl + } // st1() // ---- draw_systems() ---- /* draw the staff, skipping the staff breaks */ for (st = 0; st <= nstaff; st++) { - stl[st] = gene.st_print[st] || cur_sy.st_print[st] // staff in the line + stl[st] = cur_sy.st_print[st] // staff at start of line xstaff[st] = !stl[st] ? -1 : 0; } bar_set(); draw_lstaff(0) for (s = tsfirst; s; s = s.ts_next) { @@ -3519,17 +3683,11 @@ sy = s.sy for (st = 0; st <= nstaff; st++) { x = xstaff[st] if (x < 0) { // no staff yet if (sy.st_print[st]) { - if (s.ts_prev.bar_type) - xstaff[st] = s.ts_prev.x - else if (s.ts_next.bar_type) - xstaff[st] = s.x - else - xstaff[st] = s.ts_prev.x - + s.ts_prev.wr + xstaff[st] = st1(st, s) stl[st] = true } continue } if (sy.st_print[st] // if not staff stop @@ -3561,11 +3719,13 @@ ba.push([s, bar_bot[s.st], bar_height[s.st]]) break case C.STBRK: if (cur_sy.voices[s.v] && cur_sy.voices[s.v].range == 0) { - if (s.xmx > 14) { + if (s.xmx > 14 + && s.next // if not at end of line + && s.next.type == C.CLEF) { // and before a clef /* draw the left system if stbrk in all voices */ var nv = 0 for (var i = 0; i < voice_tb.length; i++) { if (cur_sy.voices[i] @@ -3647,10 +3807,14 @@ // bm.s2 = undefined for (s = p_voice.sym; s; s = s.next) { if (s.invis) { switch (s.type) { + case C.CLEF: + if (s.time >= staff_tb[s.st].clef.time) + staff_tb[s.st].clef = s + continue case C.KEY: p_voice.ckey = s default: continue case C.NOTE: // (beams may start on invisible notes) @@ -3685,11 +3849,12 @@ break /* drawn in draw_systems */ case C.CLEF: if (s.time >= staff_tb[st].clef.time) staff_tb[st].clef = s if (s.second - || !staff_tb[st].topbar) + || !staff_tb[st].topbar + || !gene.st_print[st]) break set_color(); set_sscale(st); anno_start(s); y = staff_tb[st].y @@ -3767,10 +3932,105 @@ /* -- draw all symbols -- */ function draw_all_sym() { var p_voice, v, n = voice_tb.length + + // draw the slurs on 2 staves + // sl = [symbol of 1st staff, symbol of 2nd staff, slur type, + function draw_sl2() { + var i, a, d, dy, dy2, dy2o, dz, n, sl + + while (1) { + sl = gene.a_sl.shift() + if (!sl) + break + + // extract the path header and the values + i = sl[3].indexOf('d="M') + 4 + output += sl[3].slice(0, i) // d="M + + a = new Float32Array(sl[3].slice(i).match(/[\d.-]+/g)) + + // update the starting point of the slur + a[1] -= staff_tb[sl[0].st].y // absolute vertical offset + +// [0][1] = M +// [2][3] [4][5] [6][7] = c +// [8] = v second curve if not dotted +// [9][10] [11][12] [13][14] = c +// +// y: 3-------5 +// / 12---10 \ +// / / \ \ +// / / \ \ 7 +// 1 14 \| 8 +// +// x: 0 2 4 6 +// 13 11 9 + + // deltas between staves, original and now + dy2o = sl[0].fmt.sysstaffsep + 24 + dy2 = staff_tb[sl[1].st].y - staff_tb[sl[0].st].y + + switch (sl[2]) { // slur type + case "//": // '~' like + case "\\\\": + + // get the middle of the '~' slur (* 2) + d = -(sl[1].prev.prev.y + staff_tb[sl[0].st].y + + sl[1].prev.next.y + staff_tb[sl[1].st].y) + - 2 * (a[1] - posy) + a[5] = d - a[5] + a[7] = d - a[7] + if (a.length > 8) { + d = sl[2][0] == '/' ? 3 : -3 + a[8] = -a[8] + a[10] = -a[3] + d + a[12] = -a[5] + d + a[14] = -a[7] + } + break + case "/\\": + case "\\/": + d = sl[2][0] == '/' + ? dy2 - dy2o - 10 + : dy2 + dy2o + 10 + a[3] += d + a[5] += d + if (a.length > 8) { + a[10] += d + a[12] += d + } + break + default: // /+, /-, \+ or \- + d = sl[2][0] == '/' ? dy2 - dy2o : -dy2 - dy2o + a[5] += d + a[7] += d + if (a.length > 8) { + a[12] -= d + a[14] -= d + } + break + } + + // output the slur + output += a[0].toFixed(1) + ' ' + a[1].toFixed(1) + + 'c' + a[2].toFixed(1) + ' ' + a[3].toFixed(1) + + ' ' + a[4].toFixed(1) + ' ' + a[5].toFixed(1) + + ' ' + a[6].toFixed(1) + ' ' + a[7].toFixed(1) + if (a.length > 8) + output += 'v' + a[8].toFixed(1) + + 'c' + a[9].toFixed(1) + + ' ' + a[10].toFixed(1) + + ' ' + a[11].toFixed(1) + + ' ' + a[12].toFixed(1) + + ' ' + a[13].toFixed(1) + + ' ' + a[14].toFixed(1) + output += '"/>\n' + } + } // draw_sl2() for (v = 0; v < n; v++) { p_voice = voice_tb[v] if (p_voice.sym && p_voice.sym.x != undefined) { @@ -3782,11 +4042,15 @@ } self.draw_all_deco() glout() // output the symbols anno_put() // before outputting the symbol annotations + set_sscale(-1) /* restore the scale */ + + if (gene.a_sl) // if slurs on two staves + draw_sl2() } /* -- set the tie directions for one voice -- */ function set_tie_dir(s) { var i, ntie, dir, sec, pit, ty, s2 Index: core/format.js ================================================================== --- core/format.js +++ core/format.js @@ -1,8 +1,8 @@ // abc2svg - format.js - formatting functions // -// Copyright (C) 2014-2022 Jean-Francois Moine +// Copyright (C) 2014-2023 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -15,19 +15,17 @@ // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with abc2svg-core. If not, see . -var font_tb = [], - font_st = {}, // font style => font_tb index for incomplete user fonts - font_scale_tb = { + var font_scale_tb = { serif: 1, serifBold: 1, 'sans-serif': 1, 'sans-serifBold': 1, Palatino: 1.1, - monospace: 1.35 + monospace: 1 }, txt_ff = "text,serif", // text font-family (serif for compatibility) fmt_lock = {} var cfmt = { @@ -109,10 +107,11 @@ stretchlast: .25, stretchstaff: true, subtitlefont: {name: txt_ff, size: 16}, subtitlespace: 3, sysstaffsep: 34, + systnames: -1, tempofont: {name: txt_ff, weight: "bold", size: 12}, textfont: {name: txt_ff, size: 16}, // textoption: undefined, textspace: 14, tieheight: 1.0, @@ -122,11 +121,11 @@ titletrim: true, // transp: 0, // global transpose // topmargin: .7 * IN, topspace: 22, tuplets: [0, 0, 0, 0], - tupletfont: {name: txt_ff, style: "italic", size: 12}, + tupletfont: {name: txt_ff, style: "italic", size: 10}, vocalfont: {name: txt_ff, weight: "bold", size: 13}, vocalspace: 10, voicefont: {name: txt_ff, weight: "bold", size: 13}, // voicescale: 1, writefields: "CMOPQsTWw", @@ -155,15 +154,17 @@ rbmin: true, shiftunison: true, slurheight: true, squarebreve: true, staffsep: true, +systnames: 1, // stemheight: true, stretchlast: true, stretchstaff: true, tieheight: true, timewarn: true, +trimsvg: 1, //true vocalspace: true } // sfmt function get_bool(param) { return !param || !/^(0|n|f)/i.test(param) // accept void as true ! @@ -194,39 +195,22 @@ font.swfac = font.size * scale } // %%xxxfont fontname|* [encoding] [size|*] function param_set_font(xxxfont, p) { - var font, n, a + var font, n, a, ft2, k // "setfont-" goes to "ufont" if (xxxfont[xxxfont.length - 2] == '-') { n = xxxfont[xxxfont.length - 1] if (n < '1' || n > '9') return xxxfont = "u" + n + "font" } - // create a new font - font = cfmt[xxxfont]; - if (!font) { // set-font- or new element - font = { - pad: 0 - } - } else { - font = { - name: font.name, - size: font.size, - weight: font.weight, - style: font.style, - box: font.box, - pad: font.pad || 0 - } - } - cfmt[xxxfont] = font; - // fill the values + font = {} a = p.match(/\s+(no)?box(\s|$)/) if (a) { // if box if (a[1]) { font.box = false // nobox font.pad = 0 @@ -273,45 +257,31 @@ if (a[1] != "*") font.size = +a[1] p = p.replace(a[0], "") } - font.fname = p // full name for scale factor - - // accept url(...) as the font name - if (p[0] == 'u' && p.slice(0, 4) == "url(") { + // accept local(..) and url(...) as the font source + if ((p[0] == 'u' && p.slice(0, 4) == "url(") + || (p[0] == 'l' && p.slice(0, 6) == "local(")) { n = p.indexOf(')', 1) if (n < 0) { syntax(1, "No end of url in font family") return } - p = p.slice(0, n + 1) - - font.src = p - font.fid = font_tb.length - font_tb.push(font) - font.normal = true - p = 'ft' + font.fid - } else { - if (p[0] == '"') { - n = p.indexOf('"', 1) - if (n < 0) { - syntax(1, "No end of string in font family") - return - } - p = p.slice(1, n) - } + + font.src = p.slice(0, n + 1) + font.fid = abc2svg.font_tb.length + abc2svg.font_tb.push(font) + font.name = 'ft' + font.fid + p = p.replace(font.src, '') + } // extract the font attributes a = p.match(/[- ]?[nN]ormal/) if (a) { font.normal = true p = p.replace(a[0], '') - if (font.weight) - font.weight = null - if (font.style) - font.style = null } // font weight a = p.match(abc2svg.ft_re) if (a) { @@ -328,17 +298,27 @@ a = p.match(/[- ]?[oO]blique/) if (a) { font.style = "oblique" p = p.replace(a[0], '') } + + if (!font.src) { // if no url(...) + if (p[0] == '"') { + n = p.indexOf('"', 1) + if (n < 0) { + syntax(1, "No end of string in font family") + return + } + p = p.slice(1, n) + } // here is the font family p = p.trim() switch (p) { case "": - case "*": return + case "*": p = ""; break case "Times-Roman": case "Times": p = "serif"; break case "Helvetica": p = "sans-serif"; break case "Courier": p = "monospace"; break case "music": p = cfmt.musicfont.name; break @@ -347,16 +327,48 @@ if (p.indexOf("Fig") > 0) font.figb = true break } } - font.name = p + if (p && !font.name) + font.name = p if (font.size) set_font_fac(font) - else - font.swfac = 0 + + // keep the previous attributes if no font name or no size + if (!font.name || !font.size) { + ft2 = cfmt[xxxfont] + for (k in ft2) { + if (!ft2.hasOwnProperty(k) + || font[k] != undefined) + continue + switch (k) { + case "fid": + case "used": + case "src": + break + case "style": + case "weight": + if (font.normal) + break + // fall thru + default: + font[k] = ft2[k] + break + } + } + if (!font.swfac) + set_font_fac(font) + } + if (font.pad == undefined) + font.pad = 0 + font.fname = font.name + if (font.weight >= 700) + font.fname += 'Bold' + + cfmt[xxxfont] = font } // get a length with a unit - return the number of pixels function get_unit(param) { var v = param.toLowerCase().match(/(-?[\d.]+)(.*)/) @@ -368,11 +380,11 @@ case "cm": return v[1] * CM case "in": return v[1] * IN case "pt": // paper point in 1/72 inch - return v[1] * .75 + return v[1] / .75 case "px": // screen pixel in 1/96 inch case "": return v[1] } return NaN @@ -405,11 +417,18 @@ fill: 'f', justify: 'j', obeylines: 'l', ragged: 'f', right: 'r', - skip: 's' + skip: 's', +// abcm2ps compatibility + "0": 'l', + "1": 'j', + "2": 'f', + "3": 'c', + "4": 's', + "5": 'r' } function get_textopt(v) { var i = v.indexOf(' ') if (i > 0) v = v.slice(0, i) @@ -512,10 +531,11 @@ case "measurenb": case "rbmax": case "rbmin": case "measrepnb": case "shiftunison": + case "systnames": v = parseInt(param) if (isNaN(v)) { syntax(1, "Bad integer value"); break } @@ -538,18 +558,20 @@ case "scale": case "slurheight": case "stemheight": case "tieheight": f = +param - if (isNaN(f)) { + if (isNaN(f) || !param || f < 0) { syntax(1, errs.bad_val, '%%' + cmd) break } switch (cmd) { case "scale": // old scale f /= .75 case "pagescale": + if (f < .1) + f = .1 // smallest scale cmd = "scale"; img.chg = true break } cfmt[cmd] = f @@ -581,10 +603,11 @@ case "straightflags": case "stretchstaff": case "timewarn": case "titlecaps": case "titleleft": + case "trimsvg": cfmt[cmd] = get_bool(param) break case "dblrepbar": param = ":: " + param // fall thru @@ -653,17 +676,19 @@ cfmt.sound = "concert" break case "writefields": set_writefields(param) break + case "volume": + cmd = "dynamic" + // fall thru case "dynamic": case "gchord": case "gstemdir": case "ornament": case "stemdir": case "vocal": - case "volume": set_pos(cmd, param) break case "font": get_font_scale(param) break @@ -764,10 +789,12 @@ curvoice.tup[2] = 1 break } break } + if (cmd[1].slice(0, 3) == "vol") + cmd[1] = "dyn" // compatibility set_pos(cmd[1], cmd[2]) break case "sounding-score": if (cfmt.sound != "play") cfmt.sound = "sounding" @@ -815,10 +842,16 @@ set_v_param("map", param) break case "voicescale": set_v_param("scale", param) break + case "unsizedsvg": + if (get_bool(param)) + user.imagesize = "" + else + delete user.imagesize + break // deprecated case "rbdbstop": v = get_bool(param) if (v && cfmt["abc-version"] >= "2.2") cfmt["abc-version"] = "1" @@ -871,16 +904,24 @@ // use the font function use_font(font) { if (!font.used) { font.used = true; if (font.fid == undefined) { // if default font - font.fid = font_tb.length - font_tb.push(font) + font.fid = abc2svg.font_tb.length + 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 + font.cw_tb = !font.name ? ssw_tb + : font.name.indexOf("ans") > 0 + ? ssw_tb // sans-serif + : font.name.indexOf("ono") > 0 + ? mw_tb // monospace + : sw_tb // serif } add_fstyle(".f" + font.fid + (cfmt.fullsvg || "") + "{" + style_font(font) + "}") if (font.src) @@ -927,15 +968,15 @@ st = st_font(font2) if (font.class) { font2.class = font.class st += ' '+ font.class } - fid = font_st[st] + fid = abc2svg.font_st[st] if (fid != undefined) - return font_tb[fid] - font_st[st] = font_tb.length // will be the font id + return abc2svg.font_tb[fid] + abc2svg.font_st[st] = abc2svg.font_tb.length // will be the font id font2.fid = font2.used = undefined font = font2 } use_font(font) return font } Index: core/front.js ================================================================== --- core/front.js +++ core/front.js @@ -1,8 +1,8 @@ // abc2svg - front.js - ABC parsing front-end // -// 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 @@ -17,11 +17,12 @@ // You should have received a copy of the GNU Lesser General Public License // along with abc2svg-core. If not, see . var sav = {}, // save global (between tunes) definitions mac = {}, // macros (m:) - maci = {} // first letters of macros + maci = {}, // first letters of macros + modone = {} // hooks done by module // translation table from the ABC draft version 2.2 var abc_utf = { "=D": "Đ", "=H": "Ħ", @@ -281,11 +282,11 @@ mac = sav.mac; maci = sav.maci; parse.tune_v_opts = null; parse.scores = null; parse.ufmt = false - delete parse.ctrl + delete parse.pq init_tune() img.chg = true; set_page(); } // end_tune() @@ -380,13 +381,18 @@ } } } // tune_filter() // export functions and/or set module hooks - if (abc2svg.modules - && (abc2svg.modules.hooks.length || abc2svg.modules.g_hooks.length)) - set_hooks() + if (abc2svg.mhooks) { + for (i in abc2svg.mhooks) { + if (!modone[i]) { + modone[i] = 1 //true + abc2svg.mhooks[i](self) + } + } + } // initialize parse.file = file; // used for errors parse.fname = in_fname @@ -435,12 +441,13 @@ parse.line.index = 0; // check if the line is a pseudo-comment or I: line0 = file[bol]; line1 = file[bol + 1] - if (line0 == '%') { - if (parse.prefix.indexOf(line1) < 0) + if ((line0 == 'I' && line1 == ':') + || line0 == '%') { + if (line0 == '%' && parse.prefix.indexOf(line1) < 0) continue // comment // change "%%abc xxxx" to "xxxx" if (file[bol + 2] == 'a' && file[bol + 3] == 'b' @@ -450,12 +457,10 @@ line0 = file[bol]; line1 = file[bol + 1] } else { pscom = true } - } else if (line0 == 'I' && line1 == ':') { - pscom = true } // pseudo-comments if (pscom) { pscom = false; @@ -533,21 +538,18 @@ parse.tune_opts[select] = opt = { t_opts: [] // v_opts: {} }; while (1) { - bol = ++eol - if (file[bol] != '%') + bol = eol + if (file[bol + 1] != '%') break - eol = file.indexOf('\n', eol); - if (file[bol + 1] != line1) + eol = file.indexOf('\n', eol + 1) + if (file[bol + 2] != line1) continue - bol += 2 - if (eol < 0) - text = file.slice(bol) - else - text = file.slice(bol, eol); + text = file.slice(bol + 3, + eol < 0 ? undefined : eol) a = text.match(/([^\s]+)\s*(.*)/) switch (a[1]) { case "tune": break case "voice": @@ -563,11 +565,11 @@ } if (parse.tune_v_opts) { opt.v_opts = parse.tune_v_opts; parse.tune_v_opts = null } - parse.eol = bol - 1 + parse.eol = bol continue case "voice": if (parse.state != 0) { syntax(1, errs.not_in_tune, "%%voice") continue @@ -739,10 +741,14 @@ txt_add = '\n'; last_info = line0 } if (include) return + if (parse.state == 1) { + syntax(1, "End of file in tune header") + get_key("C") + } if (parse.state >= 2) end_tune(); parse.state = 0 } Abc.prototype.tosvg = tosvg Index: core/gchord.js ================================================================== --- core/gchord.js +++ core/gchord.js @@ -1,8 +1,8 @@ // abc2svg - gchord.js - chord symbols // -// Copyright (C) 2014-2022 Jean-Francois Moine +// Copyright (C) 2014-2023 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -126,17 +126,10 @@ while (1) { c = text[i] if (!c) break switch (c) { - case '\\': - c = text[++i] - if (c == 'n') - break - gch.text += '\\' - if (!c) - break default: gch.text += c; i++ continue case '&': /* skip "&xxx;" */ @@ -157,10 +150,11 @@ i++; gch.text += c continue } break + case '\n': // abcm2ps compatibility case ';': break } i++ break @@ -171,40 +165,49 @@ a_gch.push(gch) } } // transpose a chord symbol -var note_names = "CDEFGAB", - acc_name = ["bb", "b", "", "#", "##"] - - function gch_tr1(p) { - var i, o, n, a, ip, b40, - tr = curvoice.tr_sco, + function gch_tr1(p, tr) { + var i, o, n, ip, csa = p.split('/') + tr = abc2svg.b40l5[(tr + 202) % 40] // transpose in the line of fifth for (i = 0; i < csa.length; i++) { // main and optional bass p = csa[i]; o = p.search(/[A-G]/) if (o < 0) continue // strange chord symbol! ip = o + 1 - a = 0 - while (p[ip] == '#' || p[ip] == '\u266f') { - a++; - ip++ - } - while (p[ip] == 'b' || p[ip] == '\u266d') { - a--; - ip++ - } - n = note_names.indexOf(p[o]) + 16 - b40 = (abc2svg.pab40(n, a) + tr + 200) % 40 - b40 = abc2svg.b40k[b40] - csa[i] = p.slice(0, o) + - note_names[abc2svg.b40_p[b40]] + - acc_name[abc2svg.b40_a[b40] + 2] + - p.slice(ip) + +// bbb fb cb gb db ab eb bb f c g d a e b f# c# g# d# a# e# b# f## +// -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + n = "FCGDAEB".indexOf(p[o]) - 1 + if (p[ip] == '#' || p[ip] == '\u266f') { + n += 7 + ip++ + } else if (p[ip] == 'b' || p[ip] == '\u266d') { + n -= 7 + ip++ + } + n += tr // transpose + +// // remove chords with double sharps/flats +// if ((!i && n > 7) // main chord +// || (i && n > 12)) // bass +// n -= 12 +// else if (i < -7) +// n += 12 + + csa[i] = p.slice(0, o) + + "FCGDAEB"[(n + 22) % 7] + + (n >= 13 ? '##' + : n >= 6 ? '#' + : n <= -9 ? 'bb' + : n <= -2 ? 'b' + : '') + + p.slice(ip) } return csa.join('/') } // gch_tr1 // parser: add the parsed list of chord symbols and annotations @@ -226,11 +229,11 @@ if (curvoice.tr_sco) { for (i = 0; i < a_gch.length; i++) { gch = a_gch[i] if (gch.type == 'g') - gch.text = gch_tr1(gch.text) + gch.text = gch_tr1(gch.text, curvoice.tr_sco) } } if (s.a_gch) s.a_gch = s.a_gch.concat(a_gch) @@ -280,12 +283,14 @@ gch.text = str2svg(gch.text) wh = gch.text.wh switch (gch.type) { case '@': break - case '^': /* above */ - case '_': /* below */ + default: +// case 'g': // chord symbol +// case '^': /* above */ +// case '_': /* below */ xspc = wh[0] * GCHPRE if (xspc > 8) xspc = 8; gch.x = -xspc; break @@ -297,16 +302,10 @@ case '>': /* right */ gch.x = 6; y_right -= wh[1]; gch.y = y_right + wh[1] / 2 break - default: // chord symbol - xspc = wh[0] * GCHPRE - if (xspc > 8) - xspc = 8; - gch.x = -xspc; - break } } /* move upwards the top and middle texts */ y_left /= 2; @@ -393,13 +392,13 @@ } break } if (an.type != '@') { - if (y > 24) + if (y >= 0) y_set(s.st, 1, x, w, y + h + pad + 2) - else if (y < 0) + else y_set(s.st, 0, x, w, y - pad) } use_font(an.font) set_font(an.font) @@ -413,11 +412,11 @@ x - 2, y + h + 2, w + 4, h + 4, s) } // draw_gchord() // draw all chord symbols function draw_all_chsy() { - var s, san1, an, i, y, w, + var s, san1, an, i, x, y, w, n_an = 0, // max number of annotations minmax = new Array(nstaff + 1) // set a vertical offset to all the chord symbols/annotations function set_an_yu(j) { @@ -434,11 +433,11 @@ if (an.pos == C.SL_ABOVE) { x = s.x + an.x w = an.text.wh[0] if (w && x + w > realwidth) x = realwidth - w // let the text in the page - y = y_get(s.st, 1, x, w) + 2 // y / staff + y = y_get(s.st, 1, x, w) // y / staff if (an.type == 'g' && y < minmax[s.st].yup) y = minmax[s.st].yup } else if (an.pos == C.SL_BELOW || an.pos == C.SL_HIDDEN) { continue @@ -489,17 +488,20 @@ if (i > n_an) n_an = i while (--i >= 0) { if (an[i].type == 'g') { an = an[i] + x = s.x + an.x w = an.text.wh[0] + if (w && x + w > realwidth) + x = realwidth - w if (an.pos == C.SL_ABOVE) { - y = y_get(s.st, true, s.x, w) + 2 + y = y_get(s.st, true, x, w) if (y > minmax[s.st].yup) minmax[s.st].yup = y } else if (an.pos == C.SL_BELOW) { - y = y_get(s.st, false, s.x, w) - 2 + y = y_get(s.st, false, x, w) - 2 if (y < minmax[s.st].ydn) minmax[s.st].ydn = y } break } Index: core/license ================================================================== --- core/license +++ core/license @@ -1,3 +1,3 @@ // abc2svg - ABC to SVG translator // @source: https://chiselapp.com/user/moinejf/repository/abc2svg -// Copyright (C) 2014-2023 Jean-Francois Moine - LGPL3+ +// Copyright (C) 2014-2024 Jean-Francois Moine - LGPL3+ Index: core/lyrics.js ================================================================== --- core/lyrics.js +++ core/lyrics.js @@ -1,8 +1,8 @@ // abc2svg - lyrics.js - lyrics // -// Copyright (C) 2014-2022 Jean-Francois Moine +// Copyright (C) 2014-2023 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -183,11 +183,11 @@ i++ continue case '-': case '_': word = p[i] - ln = 2 // line continuation + ln = p[i] == '-' ? 2 : 3 // line continuation break case '*': word = "" break default: @@ -287,11 +287,11 @@ i = s2.a_ly.length while (--i >= 0) { ly = s2.a_ly[i] if (!ly) continue - if (ly.ln != 2) + if (!ly.ln || ly.ln < 2) break } if (i >= 0) break } @@ -300,17 +300,18 @@ for (i = 0; i < a_ly.length; i++) { ly = a_ly[i] if (!ly) continue gene.curfont = ly.font - ly.t = p = str2svg(ly.t) - if (ly.ln == 2) { + ly.t = str2svg(ly.t) + p = ly.t.replace(/<[^>]*>/g, '') // remove the XML tags + if (ly.ln >= 2) { ly.shift = 0 continue } spw = cwid(' ') * ly.font.swfac - w = p.wh[0] + spw * 1.5 + w = ly.t.wh[0] if (s.type == C.GRACE) { // %%graceword shift = s.wl } else if ((p[0] >= '0' && p[0] <= '9' && p.length > 2) || p[1] == ':' || p[0] == '(' || p[0] == ')') { @@ -322,12 +323,11 @@ if (j > 0) sz = strwh(p.slice(0, j))[0] else sz = w * .2 } - w -= sz - shift = w * .4 + shift = (w - sz) * .4 if (shift > 14) shift = 14 shift += sz if (p[0] >= '0' && p[0] <= '9') { if (shift > align) @@ -339,10 +339,11 @@ shift = 14 } ly.shift = shift if (shift > wl) wl = shift // max left space + w += spw * 1.5 // space after the syllable w -= shift // right width if (w > wx) wx = w // max width } @@ -416,39 +417,40 @@ continue } if (ly.font != gene.curfont) /* font change */ gene.curfont = ly.font p = ly.t; + ln = ly.ln || 0 w = p.wh[0] shift = ly.shift if (hyflag) { - if (ly.ln == 2 && p == "_") { // '_' - p = "-" - } else if (ly.ln != 2) { // not '-' + if (ln == 3) { // '_' + ln = 2 + } else if (ln < 2) { // not '-' out_hyph(lastx, y, s.x - shift - lastx); hyflag = false; lastx = s.x + s.wr } } if (lflag - && (ly.ln != 2 || p != "_")) { // not '_' + && ln != 3) { // not '_' out_wln(lastx + 3, y, x0 - lastx + 3); lflag = false; lastx = s.x + s.wr } - if (ly.ln == 2) { // '-' or '_' + if (ln >= 2) { // '-' or '_' if (x0 == 0 && lastx > s.x - 18) lastx = s.x - 18 - if (p == '-') + if (ln == 2) // '-' hyflag = true else lflag = true; x0 = s.x - shift continue } x0 = s.x - shift; - if (ly.ln) // '-' at end + if (ln) // '-' at end hyflag = true if (user.anno_start || user.anno_stop) { s2 = { p_v: s.p_v, st: s.st, @@ -483,11 +485,11 @@ for (p_voice.s_next; s; s = s.next) { if (s.type == C.NOTE) { if (!s.a_ly) break ly = s.a_ly[j] - if (ly && ly.ln && ly.t == "_") { + if (ly && ly.ln == 3) { // '_' lflag = true; x0 = realwidth - 15 if (x0 < lastx + 12) x0 = lastx + 12 } @@ -510,22 +512,23 @@ if (y > -tsfirst.fmt.vocalspace) y = -tsfirst.fmt.vocalspace; y *= sc for (j = 0; j < nly; j++) { y -= a_h[j] * 1.1; - draw_lyric_line(p_voice, j, y) + draw_lyric_line(p_voice, j, + y + a_h[j] * .22) // (descent) } - return (y - a_h[j - 1] / 6) / sc + return y / sc } /* above the staff */ top = staff_tb[p_voice.st].topbar + tsfirst.fmt.vocalspace if (y < top) y = top; y *= sc for (j = nly; --j >= 0;) { - draw_lyric_line(p_voice, j, y); + draw_lyric_line(p_voice, j, y + a_h[j] * .22) y += a_h[j] * 1.1 } return y / sc } @@ -658,11 +661,11 @@ if (!p_voice.sym) continue st = p_voice.st; if (lyst_tb[st].a) { top = lyst_tb[st].top + 2 - for (s = p_voice.sym.next; s; s = s.next) { + for (s = p_voice.sym; s; s = s.next) { /*fixme: may have lyrics crossing a next symbol*/ if (s.a_ly) { /*fixme:should set the real width*/ y_set(st, 1, s.x - 2, 10, top) } @@ -669,11 +672,11 @@ } } if (lyst_tb[st].b) { bot = lyst_tb[st].bot - 2 if (nly_tb[p_voice.v] > 0) { - for (s = p_voice.sym.next; s; s = s.next) { + for (s = p_voice.sym; s; s = s.next) { if (s.a_ly) { /*fixme:should set the real width*/ y_set(st, 0, s.x - 2, 10, bot) } } Index: core/mei.js ================================================================== --- core/mei.js +++ core/mei.js @@ -1,8 +1,8 @@ // abc2svg - mei.js - MEI front-end // -// Copyright (C) 2019-2021 Jean-Francois Moine +// Copyright (C) 2019-2023 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -1231,10 +1231,11 @@ not1.tie_ty = ty not1.tie_e = not2 not2.tie_s = not1 } } + } } s1.ti1 = s2.ti2 = true }, // tie() trill: function(tag) { Index: core/modules.js ================================================================== --- core/modules.js +++ core/modules.js @@ -54,12 +54,10 @@ strtab: {}, temperament: { fn: 'temper' }, tropt: {}, nreq: 0, - hooks: [], - g_hooks: [], // scan the file and find the required modules // @file: ABC file // @relay: (optional) callback function for continuing the treatment // @errmsg: (optional) function to display an error message if any Index: core/music.js ================================================================== --- core/music.js +++ core/music.js @@ -1,8 +1,8 @@ // abc2svg - music.js - music generation // -// 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 @@ -238,11 +238,12 @@ if (dx1 > dx2) dx2 = dx1 // align the accidentals notes[i1].shac = notes[i2].shac = dx2 } else { notes[i1].shac = dx1 - dx1 += 7 + if (notes[i1].pit != notes[i2].pit) + dx1 += 7 notes[i2].shac = dx2 = dx1 } dx2 += 7 // shift the remaining accidentals @@ -347,16 +348,16 @@ s.ts_prev = null } s.seqst = !s.ts_prev || s.time != s.ts_prev.time || (w_tb[s.ts_prev.type] != w_tb[s.type] - && w_tb[s.ts_prev.type]) - if (!next) + && !!w_tb[s.ts_prev.type]) + if (!next || next.seqst) return next.seqst = next.time != s.time || (w_tb[s.type] != w_tb[next.type] - && w_tb[s.type]) + && !!w_tb[s.type]) if (next.seqst) { old_wl = next.wl self.set_width(next) if (next.a_ly) ly_set(next) @@ -745,11 +746,11 @@ } } /* room for the decorations */ if (s.a_dd) - wlnote += deco_width(s) + wlnote = deco_width(s, wlnote) /* space for flag if stem goes up on standalone note */ if (s.beam_st && s.beam_end && s.stem > 0 && s.nflags > 0) { if (s.wr < s.xmx + 9) @@ -781,14 +782,18 @@ wlw = wlnote if (s2) { switch (s2.type) { - case C.NOTE: /* extra space when up stem - down stem */ - if (s2.stem > 0 && s.stem < 0) { - if (wlw < 7) - wlw = 7 + case C.NOTE: + + // change the spacing when stems in reverse directions + if (s.stem * s2.stem < 0) { + if (s.stem < 0) + wlw += 5 + else + wlw -= 3 } /* make sure helper lines don't overlap */ if ((s.y > 27 && s2.y > 27) || (s.y < -3 && s2.y < -3)) { @@ -806,10 +811,13 @@ if (s2.second || s2.clef_small) break // fall thru case C.KEY: + if (s.a_gch) + wlw += 4 // have some room for the chord symbols + // fall thru case C.METER: wlw += 3 break } } @@ -832,11 +840,11 @@ xx = s.width / 2; s.wr = xx if (s.a_gch) xx = gchord_width(s, xx, xx) if (s.a_dd) - xx += deco_width(s); + xx = deco_width(s, xx) s.wl = xx return case C.BAR: bar_type = s.bar_type switch (bar_type) { @@ -866,10 +874,16 @@ && s.next.type != C.METER) s.wr = 7 else s.wr = 5 // s.notes[0].shhd = (w - 5) * -.5 + + // special case for (mainly) "|| !invisible! |:" + if (s.invis) +//fixme +// && s.prev && s.prev.bar_type) + s.wl = s.wr = 2 /* if preceeded by a grace note sequence, adjust */ s2 = s.prev if (s2 && s2.type == C.GRACE) s.wl -= 6 @@ -880,11 +894,11 @@ break } } if (s.a_dd) - s.wl += deco_width(s) + s.wl = deco_width(s, s.wl) /* have room for the repeat numbers / chord indication */ if (s.text && s.text.length < 4 && s.next && s.next.a_gch) { set_font("repeat"); @@ -897,17 +911,19 @@ case C.CLEF: // (there may be invisible clefs in empty staves) if (s.invis) { s.wl = s.wr = 1 // (!! not 0 !!) return + } + if (s.prev && s.prev.type == C.STBRK) { + s.wl = 6 + s.wr = 13 + delete s.clef_small + return } s.wl = s.clef_small ? 11 : 12 s.wr = s.clef_small ? 8 : 13 - if (s.prev && s.prev.type == C.STBRK) { - s.wl -= 6 - delete s.next.clef_small - } return case C.KEY: if (s.invis) { // if no accidental s.wl = s.wr = 0 // no width return @@ -1283,10 +1299,14 @@ st = s.st if (xl[st] == undefined) xl[st] = 0 if (wr[st] == undefined) wr[st] = 0; + if (s.prev && s.prev.st != st) { + xl[st] = xl[s.prev.st] + 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) @@ -1310,25 +1330,29 @@ set_sp_tup(stup, s2) stup = null } if (!s2.shrink) { - if (!s2.space && s2.type == C.CLEF + if (s2.type == C.CLEF && !s2.ts_prev.bar_type) { delete s2.seqst; /* no space */ s2.time = tim - } else if (s2.dur) { + } else { s2.shrink = 10 // cannot be null } } tim = s2.time if (!s) break // update the min left space per staff - for (st = 0; st < wr.length; st++) - wr[st] = 0; + s = s2 + do { + wr[s.st] = 0 + s = s.ts_next + } while (!s.seqst) + xa = maxx do { st = s2.st; xl[st] = xa if (s2.wr > wr[st]) @@ -1525,10 +1549,12 @@ if (s2.type == C.BAR) { if (s2.v == v) break continue } + if (!s2.dur) + continue s2.invis = s2.play = true if (s2.seqst && s2.ts_next.seqst) s2.seqst = false } s3 = to_rest(s3) @@ -1547,10 +1573,12 @@ if (s2.type == C.BAR) { if (s2.v == v) break continue } + if (!s2.dur) + continue s2.invis = s2.play = true if (s2.seqst && s2.ts_next.seqst) s2.seqst = false } s3 = to_rest(s3) @@ -1638,78 +1666,140 @@ t1 = t[1] + '|' + t[2].slice(0, i) t2 = t[2].slice(i) +'|' + t[3] } } // new_typ() - s = so // start of sequence + // change or add a bar for the voice in the previous line + function eol_bar(s, // bar |: + so, // start of new line + sst) { // first bar (for seqst) + var s1, s2, s3 + + // check if a bar in the previous line + for (s1 = so.ts_prev ; s1.time == s.time; s1 = s1.ts_prev) { + if (s1.v != s.v) + continue + if (s1.bar_type) { + if (s1.bar_type != '|') + return // don't change + s2 = s1 // last symbol in previous line + break + } + if (!s3) + s3 = s1.next // possible anchor for the new bar + } + if (!s2) { // if no symbol in previous line + s2 = clone(s) + if (!s3) + s3 = s + s2.next = s3 + s2.prev = s3.prev + if (s2.prev) + s2.prev.next = s2 + s3.prev = s2 + s2.ts_prev = so.ts_prev // time linkage + s2.ts_prev.ts_next = s2 + s2.ts_next = so + so.ts_prev = s2 + if (s == sst) // if first inserted bar + s2.seqst = 1 //true + if (s2.seqst) { + for (s = s2.ts_next; !s.seqst; s = s.ts_next) + ; + s2.shrink = s.shrink + s.shrink = s2.wr + s.wl + s2.space = s.space + s.space = 0 + } + delete s2.part + } + s2.bar_type = "||" + } // eol_bar() + + // check if there is a left repeat bar at start of the new line + s = so // start of new music line + while (s && s.time == so.time) { + if (s.bar_type && s.bar_type.slice(-1) == ':') { + s2 = s + break + } + s = s.ts_next + } + if (s2) { + s = s2 + while (1) { // loop on all voices + eol_bar(s2, so, s) + s2 = s2.ts_next + if (!s2 || s2.seqst) + break + } + return so + } + + s = so while (s.ts_prev && s.ts_prev.time == so.time) { s = s.ts_prev - if (s.bar_type) { - - // if repeat variant, move the new line - if (s.bar_type[0] == '[' - && s.text - && s.ts_next == so) - so = s + if (s.bar_type) s1 = s // first previous bar - } -// if (s.type == C.GRACE) -// so = s // keep the grace notes in the next line + else if (!s1 && s.type == C.GRACE && s.seqst) + so = s // if grace note after a bar + // move the start of line } - if (!s1) + if (!s1 + || !s1.bar_type + || (s1.bar_type.slice(-1) != ':' + && !s1.text)) return so - // search the insertion point in the new line - for (s = so; ; s = s.ts_next) { - switch (s.type) { -// default: -// if (w_tb[s.type]) -// break + // search the new start of the next line + for (so = s1; so.time == s1.time; so = so.ts_prev) { + switch (so.ts_prev.type) { case C.KEY: case C.METER: - case C.CLEF: +// case C.PART: + case C.TEMPO: + case C.STAVES: + case C.STBRK: continue } break } - while (s1 != so) { - if (s1.bar_type - && s1.next - && (s1.bar_type.slice(-1) == ':' - || s1.text) - && s1.bar_type != ':') { - new_type(s1) // return t1 and t2 + // put the new bar before the end of music line + s = s1 // keep first bar + while (1) { + new_type(s1) s2 = clone(s1) - s1.bar_type = t1 - delete s1.a_gch - if (t1 == '|') - delete s1.rbstop // (needed for play variant) - s2.bar_type = t2 - lkvsym(s2, s1.next) // voice - while (1) { - if (s.type != C.BAR -//fixme: pb if inverted voices - || s.v > s2.v) - break - s = s.ts_next - } - lktsym(s2, s) // time - if (s1.seqst) - s2.seqst = 1 - if (s == so) - so = s2 + s2.bar_type = t1 + s1.bar_type = t2 + s2.ts_prev = so.ts_prev + s2.ts_prev.ts_next = s2 + s2.ts_next = so + so.ts_prev = s2 + if (s1 == s) + s2.seqst = 1 //true + s2.next = s1 + if (s2.prev) + s2.prev.next = s2 + s1.prev = s2 + if (s1.rbstop) + s2.rbstop = s1.rbstop if (s1.text) { - s2.invis = true - delete s1.text - delete s1.rbstart - } - delete s1.part - delete s2.a_dd - } - s1 = s1.ts_next + s1.invis = 1 //true + delete s1.xsh + delete s2.text + delete s2.rbstart + } + delete s2.part + delete s1.a_dd + delete s1.a_gch + do { + s1 = s1.ts_next + } while (!s1.seqst && !s1.bar_type) + if (s1.seqst) + break } return so } // bardiv() // set the start of line marker @@ -1723,14 +1813,14 @@ } // set_eol() // put the warning symbols // the new symbols go in the previous line function do_warn(s) { // start of next line - var s1, s2, s3, s4 + var s1, s2, s3, s4, w // advance in the next line - for (s2 = s; s2; s2 = s2.ts_next) { + for (s2 = s; s2 && s2.time == s.time; s2 = s2.ts_next) { switch (s2.type) { case C.KEY: if (!s.fmt.keywarn || s2.invis) continue @@ -1782,18 +1872,35 @@ // care with spacing if (s3.seqst) { self.set_width(s3) s3.shrink = s3.wl s4 = s3.ts_prev - while (!s4.seqst) + w = 0 + while (1) { + if (s4.wr > w) + w = s4.wr + if (s4.seqst) + break s4 = s4.ts_prev - s3.shrink += s4.wr + } + s3.shrink += w s3.space = 0 - s4 = s3.ts_next - while (!s4.seqst) + s4 = s3 + while (1) { + if (s4.ts_next.seqst) + break + s4 = s4.ts_next + } + w = 0 + while (1) { + if (s4.wl > w) + w = s4.wl s4 = s4.ts_next - s4.shrink = s3.wr + s4.wl + if (s4.seqst) + break + } + s4.shrink = s3.wr + w } delete s3.part continue } if (w_tb[s2.type]) @@ -2127,10 +2234,12 @@ rm: cfmt.rightmargin } break case "mc_new": case "mc_end": + if (!mc) + break cfmt.leftmargin = mc.lm cfmt.rightmargin = mc.rm img.chg = 1 //true break } @@ -2139,10 +2248,11 @@ s = null } else if (!s.soln) { continue } else { s.soln = false +//fixme what if new line wanted? if (s.time == s2.time) continue // empty music line! while (!s.seqst) s = s.ts_prev } @@ -2230,78 +2340,93 @@ s.ymn = 0 break } } -// set the ottava decorations and the associated pitch shift +// set the pitch of the notes under an ottava sequence function set_ottava() { - var s, st, delta, note, g, o, + var s, s1, st, o, d, m = nstaff + 1, - staff_d = new Int16Array(new Array(m * 2)), // (-ottava) - staff_noo = new Int8Array(new Array(m)) // number of ottava values - - // add an ottava decoration - function ottava_add(s, ottava, start) { - var dc_st = ["15mb(", "8vb(", null, "8va(", "15ma("], - dc_en = ["15mb)", "8vb)", null, "8va)", "15ma)"]; - - a_dcn.push((start ? dc_st : dc_en)[2 + ottava]) - deco_cnv(s) - } - - for (st = 0; st <= nstaff; st++) { - staff_d[st] = 0; - staff_noo[st] = 0 - } - for (s = tsfirst; s; s = s.ts_next) { - st = s.st - if (s.ottava != undefined) - while (s.ottava.length) { - o = s.ottava.shift() - if (o) { - if (staff_noo[st]++ == 0) { // if first ottava - ottava_add(s, o, true) - staff_d[st] = -o * 7 - } - } else { - if (--staff_noo[st] == 0) { // if last ottava - ottava_add(s, -staff_d[st] / 7); - staff_d[st] = 0 - } - } - } + staff_d = new Int8Array(m) + + // update the pitches of a symbol + function sym_ott(s, d) { + var g, m, note + switch (s.type) { case C.REST: if (voice_tb.length == 1) break case C.NOTE: - delta = staff_d[st] - if (delta - && !s.p_v.ckey.k_drum) { + if (!s.p_v.ckey.k_drum) { for (m = s.nhd; m >= 0; m--) { note = s.notes[m]; if (!note.opit) note.opit = note.pit; - note.pit += delta + note.pit += d } } break case C.GRACE: for (g = s.extra; g; g = g.next) { - delta = staff_d[st] - if (delta - && !s.p_v.ckey.k_drum) { + if (!s.p_v.ckey.k_drum) { for (m = 0; m <= g.nhd; m++) { note = g.notes[m] if (!note.opit) note.opit = note.pit - note.pit += delta + note.pit += d + } + } + } + break + } + } // sym_ott() + + // remove the ottava decorations of a symbol + function deco_rm(s) { + for (var i = s.a_dd.length; --i >= 0;) { + if (s.a_dd[i].name.match(/1?[85][vm][ab]/)) + s.a_dd.splice(i, 1) + } + } // deco_rm() + + for (s = tsfirst; s; s = s.ts_next) { + st = s.st + o = s.ottava + if (o) { // some ottava start or stop + if (o[0]) { + if (staff_d[st] && !o[1]) { + sym_ott(s, staff_d[st]) + deco_rm(s) + continue // same ottava + } + } else if (!staff_d[st]) { + deco_rm(s) + continue // already no ottava + } + s1 = s + while (s1 && !s1.seqst) + s1 = s1.ts_prev + if (s1) { // update the previous symbols + while (s1 != s) { + if (s1.st == st) { + if (o[1]) + sym_ott(s1, -staff_d[st]) + if (o[0]) + sym_ott(s1, -o[0] * 7) } + s1 = s1.ts_next } } - break + if (o[0]) { // ottava start + staff_d[st] = -o[0] * 7 + } else { + staff_d[st] = 0 + } } + if (staff_d[st]) + sym_ott(s, staff_d[st]) } } // expand the multi-rests as needed function mrest_expand() { @@ -2329,11 +2454,11 @@ s.head = C.FULL s.fmr = 1 // full measure rest /* add the bar(s) and rest(s) */ tim = s.time + dur - s3 = s2 = s + s3 = s while (--nb > 0) { // add the bar s2 = clone(bar) delete s2.soln @@ -2340,18 +2465,20 @@ delete s2.a_gch delete s2.a_dd delete s2.text delete s2.rbstart delete s2.rbstop - lkvsym(s2, bar) + lkvsym(s2, s.next) // before symbol at end of rests s2.time = tim while (s3.time < tim) s3 = s3.ts_next // bar at end of measure while (s3 && s3.v < s.v && s3.type == C.BAR) s3 = s3.ts_next // keep in order if (s3) { + if (s3.bar_type) + s3.seqst = 0 //false lktsym(s2, s3) if (s3.type == C.BAR) delete s3.bar_num } else { s3 = s @@ -2395,10 +2522,12 @@ s3 = s3.ts_next // keep in order if (s3 && s3.seqst) break } if (s3) { + if (s3.dur) + s3.seqst = 0 //false lktsym(s4, s3) } else { s3 = s while (s3.ts_next) s3 = s3.ts_next @@ -2774,25 +2903,31 @@ /* upper and lower space needed by rests */ var rest_sp = [ [18, 18], [12, 18], [12, 12], - [6, 12], - [6, 8], + [10, 12], + [10, 10], [10, 10], /* crotchet */ - [6, 4], - [10, 0], - [10, 4], - [10, 10] + [8, 4], + [9, 0], + [9, 4], + [6, 8] ] + +// set the offsets of a rest +function roffs(s) { + s.ymx = s.y + rest_sp[5 - s.nflags][0] + s.ymn = s.y - rest_sp[5 - s.nflags][1] +} // roffs() // (possible hook) Abc.prototype.set_pitch = function(last_s) { var s, s2, g, st, delta, pitch, note, dur = C.BLEN, m = nstaff + 1, - staff_delta = new Int16Array(new Array(m * 2)), // delta clef + staff_delta = new Int16Array(m * 2), // delta clef sy = cur_sy // set the starting clefs of the staves for (st = 0; st <= nstaff; st++) { s = staff_tb[st].clef; @@ -2853,18 +2988,18 @@ s.y = 12; s.ymx = 24 + 15; s.ymn = -2 break case C.REST: - if (voice_tb.length == 1) { - s.y = 12; /* rest single voice */ -// s.ymx = 12 + 8; -// s.ymn = 12 - 8 - s.ymx = 24; + s.y = 12 + if (s.rep_nb > 1 // if measure repeat + || s.bar_mrep) { + s.ymx = 38 // (24 + 14) s.ymn = 0 break } + roffs(s) // fall thru case C.NOTE: delta = staff_delta[st] if (delta && !s.p_v.ckey.k_drum) { @@ -2872,19 +3007,10 @@ note = s.notes[m] note.opit = note.pit note.pit += delta } } -// if (s.type == C.NOTE) { -// s.ymx = 3 * (s.notes[s.nhd].pit - 18) + 4; -// s.ymn = 3 * (s.notes[0].pit - 18) - 4; -// } else { - if (s.type == C.REST) { - s.y = (((s.notes[0].pit - 18) / 2) | 0) * 6; - s.ymx = s.y + rest_sp[5 - s.nflags][0]; - s.ymn = s.y - rest_sp[5 - s.nflags][1] - } if (s.dur < dur) dur = s.dur break } } @@ -3020,11 +3146,11 @@ 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) - break + // fall thru case C.NOTE: case C.GRACE: break } @@ -3088,152 +3214,174 @@ } /* -- adjust the offset of the rests when many voices -- */ /* this function is called only once per tune */ function set_rest_offset() { - var s, s2, v, end_time, not_alone, v_s, y, ymax, ymin, - shift, dots, dx, + var s, s2, v, v_s, ymax, ymin, d, v_s_tb = [], sy = cur_sy + // set a vertical offset on a line + function loffs(d) { + return d > 0 + ? Math.ceil(d / 6) * 6 + : -Math.ceil(-d / 6) * 6 + } // loffs() + + // shift a rest to the right + function rshift() { + var dx = s2.dots ? 15 : 10 + s2.notes[0].shhd = dx + s2.xmx = dx + d = (d + v_s.d) / 2 + d = loffs(d) + s2.y += d + s2.ymx += d + s2.ymn += d + v_s.d = 0 + } // rshift() + + // -- set_rest_off -- for (s = tsfirst; s; s = s.ts_next) { if (s.invis) continue - if (s.type == C.STAVES) - sy = s.sy - if (!s.dur) - continue - v_s = v_s_tb[s.v] - if (!v_s) { - v_s = {} - v_s_tb[s.v] = v_s - } - v_s.s = s; - v_s.st = s.st; - v_s.end_time = s.time + s.dur - if (s.type != C.REST) - continue - - /* check if clash with previous symbols */ - ymin = -127; - ymax = 127; - not_alone = dots = false - for (v = 0; v <= v_s_tb.length; v++) { - v_s = v_s_tb[v] - if (!v_s || !v_s.s - || v_s.st != s.st - || v == s.v) - continue - if (v_s.end_time <= s.time) - continue - not_alone = true; - s2 = v_s.s - if (sy.voices[v].range < sy.voices[s.v].range) { - if (s2.time == s.time) { - if (s2.ymn < ymax) { - ymax = s2.ymn - if (s2.dots) - dots = true - } - } else { - if (s2.y < ymax) - ymax = s2.y - } - } else { - if (s2.time == s.time) { - if (s2.ymx > ymin) { - ymin = s2.ymx - if (s2.dots) - dots = true - } - } else { - if (s2.y > ymin) - ymin = s2.y - } - } - } - - /* check if clash with next symbols */ - end_time = s.time + s.dur - for (s2 = s.ts_next; s2; s2 = s2.ts_next) { - if (s2.time >= end_time) - break - if (s2.st != s.st -// || (s2.type != C.NOTE && s2.type != C.REST) - || !s2.dur - || s2.invis) - continue - not_alone = true - if (sy.voices[s2.v].range < sy.voices[s.v].range) { - if (s2.time == s.time) { - if (s2.ymn < ymax) { - ymax = s2.ymn - if (s2.dots) - dots = true - } - } else { - if (s2.y < ymax) - ymax = s2.y - } - } else { - if (s2.time == s.time) { - if (s2.ymx > ymin) { - ymin = s2.ymx - if (s2.dots) - dots = true - } - } else { - if (s2.y > ymin) - ymin = s2.y - } - } - } - if (!not_alone) { - s.y = 12; - s.ymx = 24; - s.ymn = 0 - continue - } - if (ymax == 127 && s.y < 12) { - shift = 12 - s.y - s.y += shift; - s.ymx += shift; - s.ymn += shift - } - if (ymin == -127 && s.y > 12) { - shift = s.y - 12 - s.y -= shift; - s.ymx -= shift; - s.ymn -= shift - } - shift = ymax - s.ymx - if (shift < 0) { - shift = Math.ceil(-shift / 6) * 6 - if (s.ymn - shift >= ymin) { - s.y -= shift; - s.ymx -= shift; - s.ymn -= shift - continue - } - dx = dots ? 15 : 10; - s.notes[0].shhd = dx; - s.xmx = dx - continue - } - shift = ymin - s.ymn - if (shift > 0) { - shift = Math.ceil(shift / 6) * 6 - if (s.ymx + shift <= ymax) { - s.y += shift; - s.ymx += shift; - s.ymn += shift - continue - } - dx = dots ? 15 : 10; - s.notes[0].shhd = dx; - s.xmx = dx - continue + switch (s.type) { + case C.STAVES: + sy = s.sy + // fall thru + default: + continue + case C.REST: + if (s.invis || !s.multi) + continue + v_s = v_s_tb[s.v] + if (!v_s) { + v_s_tb[s.v] = v_s = { d: 0} + } else if (v_s.d) { + s2 = v_s.s // set the offsets of the previous rest + d = loffs(v_s.d) + s2.y += d + s2.ymx += d + s2.ymn += d + v_s.d = 0 + } + + d = s.multi > 0 ? 0 : 24 + if (s.prev && s.prev.type == C.NOTE) + d = (s.next && s.next.type == C.NOTE) + ? (s.prev.y + s.next.y) / 2 + : s.prev.y + else if (s.next && s.next.type == C.NOTE) + d = s.next.y + else if (s.prev && s.prev.type == C.REST) + d = s.prev.y + if (s.multi > 0) { + if (d >= 12) + v_s.d = d - s.y + } else { + if (d <= 12) + v_s.d = d - s.y + } + + 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 + case C.NOTE: + if (s.invis || !s.multi) + continue + break + } + + // check if any clash with a rest + for (v = 0; v < v_s_tb.length; v++) { + v_s = v_s_tb[v] + if (!v_s + || v_s.st != s.st + || v == s.v + || v_s.end_time <= s.time) + continue + s2 = v_s.s // rest + if (sy.voices[v].range > sy.voices[s.v].range) { + if (s2.ymx + v_s.d <= s.ymn) + continue + d = s.ymn - s2.ymx // rest must go down +// if (s2.time < s.time) { + if (s.type == C.REST) { + if (!v_s_tb[s.v]) + v_s_tb[s.v] = {d: 0} + if (v_s_tb[s.v].d < 6) + v_s_tb[s.v].d = 6 + d = -6 + } else { + d /= 2 + if (s2.fmr) + d -= 6 + } +// } + if (v_s.d) { + if (v_s.d > 0) { // if it was go up + rshift() // shift the rest + continue + } + if (d >= v_s.d) + continue + } + } else { + if (s2.ymn + v_s.d >= s.ymx) + continue + d = s.ymx - s2.ymn // rest must go up + if (s.type == C.REST // if rest + && s2 == s.ts_prev // just under a rest + && s.y == s2.y) { // at a same offset + if (!v_s_tb[s.v]) + v_s_tb[s.v] = {d: 0} + if (v_s_tb[s.v].d > -6) + v_s_tb[s.v].d = -6 + d = 6 + } else if (s2.time < s.time) { + d = s.ymx - s2.y + } + if (v_s.d) { + if (v_s.d < 0) { // if it was go down + rshift() // shift the rest + continue + } + if (d <= v_s.d) + continue + } + } + v_s.d = d + } + } + + // update the offsets of the last rests + for (v = 0; v < v_s_tb.length; v++) { + v_s = v_s_tb[v] + if (v_s && v_s.d) { + s2 = v_s.s + d = loffs(v_s.d) + s2.y += d + s2.ymx += d + s2.ymn += d } } } /* -- create a starting symbol -- */ @@ -3288,14 +3436,14 @@ break case C.METER: s.p_v.meter = s insert_meter = cfmt.writefields.indexOf('M') >= 0 && s.a_meter.length - if (!insert_meter && s.part) - s.next.part = s.part break } + if (s.part) + s.next.part = s.part unlksym(s) // fall thru case C.TEMPO: case C.BLOCK: case C.REMARK: @@ -3343,13 +3491,15 @@ if (last_s) last_s.ts_prev = s delete s.clef_small; delete s.part s.second = cur_sy.voices[v].second -// (fixme: needed for sample5 X:3 Fugue & staffnonote.xhtml) +// (fixme: needed for sample5 X:3 Fugue & staffnonote.html) if (!cur_sy.st_print[st]) s.invis = true + else if (!s.clef_none) + delete s.invis s.fmt = fmt } /* add keysig */ for (v = 0; v < nv; v++) { @@ -3441,10 +3591,16 @@ s = tsfirst s.seqst = true for (s = last_s; s.ts_next && !s.ts_next.seqst; s = s.ts_next) ; + if (s.ts_next // a bit further in case different keys per voice + && s.ts_next.type != C.CLEF // (the clef may move in allsymwidth) + && !s.tp // (start of a tuplet) + && !s.ts_next.a_ly) // (don't update next .shrink) + for (s = s.ts_next; s.ts_next && !s.ts_next.seqst; s = s.ts_next) + ; s2 = s.ts_next s.ts_next = null set_allsymwidth() s.ts_next = s2 } // init_music_line() @@ -3549,11 +3705,11 @@ } nflags = s.nflags if (s.ntrem) - nflags += s.ntrem + nflags -= s.ntrem if (s.type == C.REST && s.beam_end && !s.beam_on) { // s.beam_end = false; start_flag = true } @@ -3760,24 +3916,27 @@ /* -- return the left indentation of the staves -- */ function set_indent(lsh) { var st, v, w, p_voice, p, i, j, font, vnt = 0, - nv = voice_tb.length + fmt = tsnext ? tsnext.fmt : cfmt // name or subname? - for (v = 0; v < nv; v++) { + if (fmt.systnames) { // display the 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.new_name) { + if (p_voice.nm + && (p_voice.new_name || fmt.systnames == 2)) { vnt = 2 // full name break } if (p_voice.snm) vnt = 1 // subname + } } gene.vnt = vnt // voice name type for draw return vnt == 2 ? lsh[0] : lsh[1] } @@ -4602,20 +4761,29 @@ case "scale": case "staffwidth": self.set_format(s.subtype, s.param) break case "mc_start": // multicol start + if (multicol) { + error(1, s, "No end of the previous %%multicol") + break + } multicol = { + state: parse.state, posy: posy, maxy: posy, lm: cfmt.leftmargin, rm: cfmt.rightmargin, w: cfmt.pagewidth, sc: cfmt.scale } break case "mc_new": // multicol new + if (!multicol || multicol.state != parse.state) { + error(1, s, "%%multicol new without start") + break + } if (posy > multicol.maxy) multicol.maxy = posy cfmt.leftmargin = multicol.lm cfmt.rightmargin = multicol.rm cfmt.pagewidth = multicol.w @@ -4622,10 +4790,14 @@ cfmt.scale = multicol.sc posy = multicol.posy img.chg = 1 //true break case "mc_end": // multicol end + if (!multicol || multicol.state != parse.state) { + error(1, s, "%%multicol end without start") + break + } if (posy < multicol.maxy) posy = multicol.maxy cfmt.leftmargin = multicol.lm cfmt.rightmargin = multicol.rm cfmt.pagewidth = multicol.w @@ -4666,29 +4838,14 @@ vskip(s.sk); break } } -// -- move some symbols of an empty staff to the next one -- -function sym_staff_move(st) { - for (var s = tsfirst; s; s = s.ts_next) { - if (s.nl) - break - if (s.st == st - && s.type != C.CLEF) { - s.st++ - if (s.type != C.TEMPO) - s.invis = true - } - } -} // sym_staff_move() - /* -- define the start and end of a piece of tune -- */ /* tsnext becomes the beginning of the next line */ function set_piece() { - var s, last, p_voice, st, v, nv, tmp, - non_empty = [], + var s, last, p_voice, st, v, nv, tmp, non_empty, non_empty_gl = [], sy = cur_sy function reset_staff(st) { var p_staff = staff_tb[st], @@ -4768,11 +4925,12 @@ // define the helper lines p_staff.hll = 17 + i * 2 // pitch of lowest note // without helper line // ('D' when standard staff) - p_staff.hlmap = new Int8Array(new Array((l - i + 1) * 2)) + 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 @@ -4798,10 +4956,11 @@ /* reset the staves */ nstaff = sy.nstaff for (st = 0; st <= nstaff; st++) reset_staff(st); + non_empty = new Uint8Array(nstaff + 1) /* * search the next end of line, * and mark the empty staves */ @@ -4809,15 +4968,15 @@ if (s.nl) break switch (s.type) { case C.STAVES: set_brace(); - sy.st_print = new Uint8Array(non_empty); + sy.st_print = non_empty sy = s.sy; while (nstaff < sy.nstaff) reset_staff(++nstaff) - non_empty = [] + non_empty = new Uint8Array(nstaff + 1) continue // the block symbols will be treated after music line generation case C.BLOCK: if (!s.play) { @@ -4873,31 +5032,15 @@ } tsnext = s; /* set the last empty staves */ set_brace() - sy.st_print = new Uint8Array(non_empty); + sy.st_print = non_empty /* define the offsets of the measure bars */ set_top_bot() - // move the symbols of the empty staves to the next staff - for (st = 0; st < nstaff; st++) { - if (!non_empty_gl[st]) - sym_staff_move(st) - } - - // set a null height if the last staff is empty - if (!non_empty_gl[nstaff]) - staff_tb[nstaff].topbar = 0 - - /* initialize the music line */ - init_music_line(); - - // keep the array of the staves to be printed - gene.st_print = new Uint8Array(non_empty_gl) - // if not the end of the tune, set the end of the music line if (tsnext) { s = tsnext; delete s.nl; last = s.ts_prev; @@ -4907,11 +5050,11 @@ nv = voice_tb.length for (v = 0; v < nv; v++) { p_voice = voice_tb[v] if (p_voice.sym && p_voice.sym.time <= tsnext.time) { - for (s = tsnext.ts_prev; s; s = s.ts_prev) { + for (s = last; s; s = s.ts_prev) { if (s.v == v) { p_voice.s_next = s.next; s.next = null; break } @@ -4921,35 +5064,56 @@ } p_voice.s_next = p_voice.sym; p_voice.sym = null } } + + // initialize the music line + init_music_line() + + // keep the array of the staves to be printed + gene.st_print = non_empty_gl } /* -- position the symbols along the staff -- */ // (possible hook) Abc.prototype.set_sym_glue = function(width) { - var s, g, ll, x, - some_grace, - spf, // spacing factor + var g, x, some_grace, stretch, + cnt = 4, xmin = 0, // sigma shrink = minimum spacing xx = 0, // sigma natural spacing xs = 0, // sigma unexpandable elements with no space - xse = 0 // sigma unexpandable elements with space + xse = 0, // sigma unexpandable elements with space + ll = !tsnext || // last line? yes + (tsnext.type == C.BLOCK // no, but followed by %%command + && !tsnext.play) + || blocks.length, // (abcm2ps compatibility) + s = tsfirst, + spf = 1, // spacing factor + xx0 = 0 /* calculate the whole space of the symbols */ - for (s = tsfirst; s; s = s.ts_next) { + for ( ; s; s = s.ts_next) { if (s.type == C.GRACE && !some_grace) some_grace = s if (s.seqst) { xmin += s.shrink + if (xmin > width) { + error(1, s, "Line too much shrunk $1 $2 $3", + xmin.toFixed(1), + xx.toFixed(1), + width.toFixed(1)) + break + } if (s.space) { if (s.space < s.shrink) { xse += s.shrink; xx += s.shrink } else { +// xx += s.space * spf + s.shrink * (1 - spf) xx += s.space + xx0 += s.shrink } } else { xs += s.shrink } } @@ -4959,38 +5123,43 @@ if (!xx) { realwidth = 0 return } - // last line? - ll = !tsnext || // yes - (tsnext.type == C.BLOCK // no, but followed by %%command - && !tsnext.play) - || blocks.length // (abcm2ps compatibility) + // stretch or not? + s = tsfirst + + if (ll) { + if ((xx - xx0 + xs) / width > (1 - s.fmt.stretchlast)) + stretch = 1 //true + } else if (s.fmt.stretchstaff) { + stretch = 1 //true + } // strong shrink - s = tsfirst if (xmin >= width) { - if (xmin > width) - error(1, s, "Line too much shrunk $1 $2 $3", - xmin.toFixed(1), - xx.toFixed(1), - width.toFixed(1)); x = 0 for ( ; s; s = s.ts_next) { if (s.seqst) x += s.shrink; s.x = x } // realwidth = width - spf_last = 0 - } else if ((ll && xx + xs > width * (1 - s.fmt.stretchlast)) - || (!ll && (xx + xs > width || s.fmt.stretchstaff))) { - if (xx == xse) // if no space - xx += 5 - for (var cnt = 4; --cnt >= 0; ) { - spf = (width - xs - xse) / (xx - xse); + spf_last = .65 + } else { + if (stretch) { + if (xx == xse) // if no space + xx += 10 + spf = (width - xs - xse) / (xx - xse) + } else { + spf = spf_last + if (ll && spf < s.fmt.stretchlast) + spf = s.fmt.stretchlast + if (spf > (width - xs) / xx) + spf = (width - xs) / xx + } + while (--cnt >= 0) { xx = 0; xse = 0; x = 0 for (s = tsfirst; s; s = s.ts_next) { if (s.seqst) { @@ -5007,26 +5176,19 @@ x += s.shrink } } s.x = x } + if (!stretch && x < width) + break if (Math.abs(x - width) < 0.1) break + if (xx == xse) // if no space + xx += 10 + spf = (width - xs - xse) / (xx - xse) } spf_last = spf - } else { // shorter line - spf = 1 - s.fmt.maxshrink - if (spf_last && xx * spf_last + xs < width) - spf = spf_last - x = 0 - for ( ; s; s = s.ts_next) { - if (s.seqst) - x += s.space <= s.shrink ? - s.shrink : - s.shrink * (1 - spf) + s.space * spf - s.x = x - } } realwidth = x /* set the x offsets of the grace notes */ for (s = some_grace; s; s = s.ts_next) { @@ -5047,13 +5209,16 @@ v = voice_tb.length // set the first symbol of each voice while (--v >= 0) { p_v = voice_tb[v] - if (p_v.sym && p_v.s_prev) + if (p_v.sym && p_v.s_prev) { p_v.sym.prev = p_v.s_prev + p_v.s_prev.next = p_v.sym + } s = p_v.s_next // (set in set_piece) + p_v.s_next = null p_v.sym = s if (s) { if (s.prev) s.prev.next = s p_v.s_prev = s.prev // (save for play) @@ -5119,20 +5284,21 @@ for (v = 0; v < nv; v++) set_beams(voice_tb[v].sym); /* decide on beams */ self.set_stems() // set the stem lengths - gen_init() // output the blocks and define the page layout - if (!tsfirst) - return - set_acc_shft() // set the horizontal offset of accidentals if (nv > 1) { // if many voices set_rest_offset(); /* set the vertical offset of rests */ set_overlap(); /* shift the notes on voice overlap */ } set_allsymwidth(1) // set the width of all symbols + + // output the blocks and define the page layout + gen_init() + if (!tsfirst) + return lsh = get_lshift() /* if single line, adjust the page width */ if (cfmt.singleline) { @@ -5155,30 +5321,34 @@ ts1st = tsfirst v = nv while (--v >= 0) voice_tb[v].osym = voice_tb[v].sym - spf_last = 0 // last spacing factor + spf_last = .65 // last spacing factor while (1) { /* loop per music line */ set_piece(); indent = set_indent(lsh) if (!line_height && cfmt.indent && indent < cfmt.indent) indent = cfmt.indent self.set_sym_glue(lwidth - indent) if (realwidth) { - if (indent) - posx += indent; + if (img.wx < realwidth) + img.wx = realwidth + if (indent) { + img.wx += indent + posx += indent + } draw_sym_near(); // delayed output line_height = set_staff(); + if (line_height) { // if some music draw_systems(indent); draw_all_sym(); delayed_update(); -//fixme: KO if at start of tune (the header is generated) - if (output) // if some music vskip(line_height) + } if (indent) posx -= indent; } blk_flush() Index: core/parse.js ================================================================== --- core/parse.js +++ core/parse.js @@ -1,8 +1,8 @@ // abc2svg - parse.js - ABC parse // -// 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 @@ -73,10 +73,11 @@ s.clef_line = 4 break case 'n': // none i = 4 s.invis = true + s.clef_none = 1 //true break case 't': if (clef_def[1] == 'e') { // tenor s.clef_type = "c"; s.clef_line = 4 @@ -89,15 +90,10 @@ case 'p': i = 4 case 'P': // perc s.clef_type = "p"; s.clef_line = 3; - curvoice.key.k_sf = 0; // no accidental - curvoice.ckey.k_sf = 0 - curvoice.ckey.k_map = abc2svg.keys[7] - curvoice.ckey.k_b40 = 2 - curvoice.ckey.k_drum = true // no transpose break default: syntax(1, "Unknown clef '$1'", clef_def) return //undefined } @@ -188,11 +184,11 @@ nt.acc = a return an } // set the microtonal accidental after transposition - switch (a) { + switch (an) { case -2: if (n > 0) n -= d * 2 else n -= d @@ -329,19 +325,23 @@ return "||||||||||||||||".slice(0, n) } // create a block symbol in the tune body function new_block(subtype) { - var s = { + var c_v, + 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) @@ -529,10 +529,14 @@ if (isNaN(val) || val < .3 || val > 2) syntax(1, "Bad %%staffscale value") else curvoice.staffscale = val break + case "tacet=": + val = a.shift() + curvoice.tacet = val || undefined + break case "transpose=": // (abcMIDI compatibility) if (cfmt.nedo) { syntax(1, errs.notransp) break } @@ -580,19 +584,11 @@ get_clef(s) } } // if transposition - if (tr_p) { - curvoice.tr_p = tr_p // keep this key signature - tr_p = (curvoice.score | 0) - + (curvoice.shift | 0) - + (cfmt.transp | 0) - if (tr_p) - curvoice.tr_sco = tr_p // b40 interval - else if (curvoice.tr_sco) - curvoice.tr_sco = 0 + if (tr_p & 2) { // curvoice.tr_sco is set in key_trans() tr_p = (curvoice.sound | 0) + (curvoice.shift | 0) if (tr_p) curvoice.tr_snd = abc2svg.b40m(tr_p + 122) - 36 // semi-tone interval else if (curvoice.tr_snd) @@ -910,11 +906,11 @@ if (i >= p.length || p[i + 1] == '(') /* "M:5 (2/4+3/4)" */ break meter.top += p[i++] } - m1 = +meter.top + m1 = eval(meter.top) break } if (!in_parenth) { if (meter.bot) m2 = +meter.bot @@ -976,10 +972,52 @@ // set the meter of the overlay voices for (p_v = curvoice.voice_down; p_v; p_v = p_v.voice_down) p_v.wmeasure = wmeasure } } + +// link P: or Q: +function link_pq(s, text) { + var p_v, s2 + + if (curvoice.v == par_sy.top_voice) { + sym_link(s) + } else if (voice_tb[par_sy.top_voice].time == s.time) { + p_v = curvoice + curvoice = voice_tb[par_sy.top_voice] + sym_link(s) + curvoice = p_v + } else if (voice_tb[par_sy.top_voice].time > s.time) { + p_v = voice_tb[par_sy.top_voice] + for (s2 = p_v.sym; ; s2 = s2.next) { + if (s2.time >= s.time) { + set_ref(s) + s.fmt = cfmt + s.next = s2 + s.prev = s2.prev + if (s2.prev) + s.prev.next = s + else + p_v.sym = s + s2.prev = s + s.v = s2.v + s.p_v = p_v + s.st = p_v.st + break + } + } + } else { + set_ref(s) + s.fmt = cfmt + if (!parse.pq_d) + parse.pq_d = [] + parse.pq_d.push(s) // delayed insertion + } + if (!parse.pq) + parse.pq = {} + parse.pq[text] = s.time +} // link_pq() /* Q: tempo */ function new_tempo(text) { var i, c, d, nd, txt = text, // (for info.Q) @@ -1069,23 +1107,18 @@ info.Q = txt glovar.tempo = s return } - var tim = curvoice.time if (!glovar.tempo) syntax(0, "No previous tempo") - if (!parse.ctrl) - parse.ctrl = {} - if (!parse.ctrl[tim]) - parse.ctrl[tim] = {} - parse.ctrl[tim].tempo = s - s.v = par_sy.top_voice - s.p_v = voice_tb[s.v] - s.fmt = cfmt - s.st = s.p_v.st - s.time = tim + s.time = curvoice.time + text = 'Q' + s.time + if (parse.pq + && parse.pq[text] == s.time) + return // already seen + link_pq(s, text) } // treat the information fields which may embedded function do_info(info_type, text) { var s, d1, d2, a, vid, tim, v, p_v @@ -1149,29 +1182,26 @@ break if (parse.state == 1) { info.P = text break } - - // memorize the part in the control - tim = curvoice.time s = { + type: C.PART, text: text, - time: tim - } - set_ref(s) - if (cfmt.writefields.indexOf(info_type) < 0) - s.invis = true - if (!parse.ctrl) - parse.ctrl = {} - if (!parse.ctrl[tim]) - parse.ctrl[tim] = {} - parse.ctrl[tim].part = s - s.v = par_sy.top_voice - s.p_v = voice_tb[s.v] - s.fmt = cfmt - s.st = s.p_v.st + time: curvoice.time + } + tim = parse.pq && parse.pq[text] // time of previous P: with same text + if (tim == s.time) + break // already seen + if (tim != null) { + syntax(1, "Misplaced P:") // different dates + break + } + + if (cfmt.writefields.indexOf('P') < 0) + s.invis = 1 //true + link_pq(s, text) break case 'Q': if (!parse.state) break new_tempo(text) @@ -1233,27 +1263,11 @@ s2 = s2.prev; time = s2.time; auto_time = curvoice.time - time fac = curvoice.wmeasure / auto_time - /* remove the invisible rest at start of tune */ - if (!time) { - while (s2 && !s2.dur) - s2 = s2.next - if (s2 && s2.type == C.REST - && s2.invis) { - time += s2.dur * fac - if (s2.prev) - s2.prev.next = s2.next - else - curvoice.sym = s2.next - if (s2.next) - s2.next.prev = s2.prev; - s2 = s2.next - } - } - if (curvoice.wmeasure == auto_time) + if (fac == 1) return /* already good duration */ for ( ; s2; s2 = s2.next) { s2.time = time if (!s2.dur || s2.grace) @@ -1313,29 +1327,10 @@ if (a_gch) csan_add(s) if (a_dcn.length) deco_cnv(s) - // set the start/stop of ottava - if (parse.ottava.length) { - s2 = s - if (curvoice.cst != curvoice.st) { // if staff change - s2 = { - type: C.SPACE, // put the decoration on a ... - fname: parse.fname, - istart: parse.bol + line.index, - dur: 0, - multi: 0, - invis: true, - width: 1 // .. small space - } - sym_link(s2) - } - s2.ottava = parse.ottava - parse.ottava = [] - } - /* if the last element is '[', it may start * a chord or an embedded header */ if (bar_type.slice(-1) == '[' && !(/[0-9" ]/.test(c))) { // " bar_type = bar_type.slice(0, -1); @@ -1384,11 +1379,11 @@ && bar_type != '[') bar_type = bar_type.slice(0, -1) // there cannot be variants on a left repeat bar if (bar_type.slice(-1) == ':') { // left repeat - s.rbstop = 2 // stop the bracket + s.rbstop = 1 // end the bracket if (s.text) { syntax(1, "Variant ending on a left repeat bar") delete s.text } curvoice.tie_s_rep = null // no tie anymore on new variant @@ -1406,52 +1401,45 @@ } else { curvoice.tie_s = curvoice.tie_s_rep if (curvoice.acc_tie_rep) curvoice.acc_tie = curvoice.acc_tie_rep.slice() } + if (curvoice.norepbra + && !curvoice.second) + s.norepbra = 1 //true } - if (s.rbstart - && curvoice.norepbra - && !curvoice.second) - s.norepbra = true - if (curvoice.ulen < 0) // L:auto adjust_dur(s); - if (bar_type == "[" || bar_type == "|:") { + // merge ":| |:" into "::" and other cases + if ((bar_type == "[" || bar_type == "|:") + && !curvoice.eoln + && !s.a_gch && !s.invis) { // no annotation nor invisible s2 = curvoice.last_sym - if (s2 && s2.time == curvoice.time) { - - // search if a previous bar at this time - do { - if (s2.type == C.BAR) - break - if (w_tb[s2.type]) // symbol with a width - break - s2 = s2.prev - } while (s2) - if (s2 && s2.type == C.BAR) { + + // if the previous symbol is also a bar + if (s2 && s2.type == C.BAR) { // && !s2.a_gch && !s2.a_dd // && !s.a_gch && !s.a_dd) { - // remove the invisible repeat bars + // remove the invisible variant bars // when no shift is needed if ((bar_type == "[" - && !s2.text - && (!curvoice.st - || (par_sy.staves[curvoice.st - 1].flags & STOP_BAR) - || s.norepbra)) - || s2.bar_type == "|") { // "|" + "|:" => "|:" - if (bar_type != "[") - s2.bar_type = bar_type - if (s.text) + && !s2.text) + || s.norepbra) { + if (s.text) { s2.text = s.text - if (s.a_gch) - s2.a_gch = s.a_gch + if (curvoice.st && !s.norepbra + && !(par_sy.staves[curvoice.st - 1] + .flags & STOP_BAR)) + s2.xsh = 4 // volta shift + } +// if (s.a_gch) +// s2.a_gch = s.a_gch if (s.norepbra) - s2.norepbra = s.norepbra + s2.norepbra = 1 //true if (s.rbstart) s2.rbstart = s.rbstart if (s.rbstop) s2.rbstop = s.rbstop //--fixme: pb when on next line and empty staff above @@ -1458,22 +1446,17 @@ return } // merge back-to-back repeat bars if (bar_type == "|:") { - if (s2.bar_type == ":|") { + switch (s2.bar_type) { + case ":|": // :| + |: => :: s2.bar_type = "::"; s2.rbstop = 2 return } - if (s2.bar_type == "||") { - s2.bar_type = "||:"; - s2.rbstop = 2 - return - } } - } } } /* set some flags */ switch (bar_type) { @@ -1504,32 +1487,15 @@ sym_link(s); s.st = curvoice.st /* original staff */ - /* if repeat bar and shift, add a repeat bar */ - if (s.rbstart - && bar_type != "[" - && !curvoice.norepbra - && s.st > 0 - && !(par_sy.staves[s.st - 1].flags & STOP_BAR)) { - s2 = { - type: C.BAR, - fname: s.fname, - istart: s.istart, - iend: s.iend, - bar_type: "[", - multi: 0, - invis: true, - text: s.text, - rbstart: 2 - } - sym_link(s2); - s2.st = s.st - delete s.text; - s.rbstart = 0 - } + // possibly shift the volta bracket if not on the first staff + if (s.text && s.st > 0 && !s.norepbra + && !(par_sy.staves[s.st - 1].flags & STOP_BAR) + && bar_type != '[') + s.xsh = 4 // volta shift if (!s.bar_dotted && !s.invis) curvoice.acc = [] // no accidental anymore } @@ -1791,32 +1757,41 @@ // 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(note, acc) { - var pit = note.pit, - nn = not2abc(pit, acc), - map = maps[curvoice.map] // never null +function set_map(p_v, note, acc, + trp) { // transpose done + 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[(pit + 75 - - curvoice.ckey.k_sf * 11) % 7] + nn = 'k' + ntb[(note.pit + 75 - + p_v.ckey.k_sf * 11) % 7] if (!map[nn]) { nn = 'all' // 'all' if (!map[nn]) return } } } - note.map = map = map[nn] - if (map[1]) { // if print map - note.pit = pit = map[1].pit - note.acc = map[1].acc + map = map[nn] + if (!trp) { // transpose not done yet + if (map[1]) { // if note shift + 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] nn = map[3] if (nn) // if play map note.midi = pit2mid(nn.pit + 19, nn.acc) @@ -1854,10 +1829,13 @@ line.index++ return ty + C.SL_ABOVE case ",": line.index++ return ty + C.SL_BELOW + case '?': // slur between staves (like ~) + line.index++ + return ty + C.SL_CENTER } return ty + C.SL_AUTO } // on end of slur, create the slur @@ -1989,11 +1967,10 @@ if (not1.midi == mid && (!se || !not1.tie_e)) { // (if unison) not2.tie_s = not1 not2.s = s - delete not2.acc // (if transposing) if (se) { not1.tie_e = not2 not1.s = tie_s } nt++ @@ -2093,10 +2070,14 @@ pit: 18, dur: s.dur }] } else { glovar.mrest_p = true + if (par_sy.voices.length == 1) { + s.tacet = curvoice.tacet + delete s.invis // show the 'H' when 'Xn' + } } break case 'y': s.type = C.SPACE; s.invis = true; @@ -2200,29 +2181,29 @@ } if (acc_tie && acc_tie[apit]) i = acc_tie[apit] // tied note - // map - if (curvoice.map - && maps[curvoice.map]) - set_map(note, i) - // set the MIDI pitch if (!note.midi) // if not map play 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) { i = nt_trans(note, i) if (i == -3) { // if triple sharp/flat error(1, s, "triple sharp/flat") i = note.acc > 0 ? 1 : -1 note.pit += i note.acc = i } dpit = note.pit + 19 - apit + } } if (curvoice.tr_snd) note.midi += curvoice.tr_snd //fixme: does not work if transposition @@ -2456,17 +2437,12 @@ if (a_dcn_sav.length) { a_dcn = a_dcn_sav deco_cnv(s, s.prev) } - if (parse.ottava.length) { - if (grace) - grace.ottava = parse.ottava - else - s.ottava = parse.ottava - parse.ottava = [] - } + if (grace && s.ottava) + grace.ottava = s.ottava if (parse.stemless) s.stemless = true s.iend = parse.bol + line.index return s } @@ -2519,16 +2495,11 @@ } if (c == '!') break dcn += c } - if (ottava[dcn] != undefined) { - glovar.ottava = true; - parse.ottava.push(ottava[dcn]) - } else { - a_dcn.push(dcn) - } + a_dcn.push(dcn) } // get_deco() // characters in the music line (ASCII only) var nil = "0", char_tb = [ @@ -2565,13 +2536,11 @@ "d", "d", "d", "d", /* p q r s */ "d", "!upbow!", "!downbow!", "d", /* t u v w */ "n", "n", "n", "{", /* x y z { */ "|", "}", "!gmark!", nil, /* | } ~ (del) */ -], - ottava = {"8va(":1, "8va)":0, "15ma(":2, "15ma)":0, - "8vb(":-1, "8vb)":0, "15mb(":-2, "15mb)":0} +] // char_tb[] function parse_music_line() { var grace, last_note_sav, a_dcn_sav, no_eol, s, tps, tp = [], tpn = -1, Index: core/subs.js ================================================================== --- core/subs.js +++ core/subs.js @@ -1,8 +1,8 @@ // abc2svg - subs.js - text output // -// Copyright (C) 2014-2022 Jean-Francois Moine +// Copyright (C) 2014-2023 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -84,23 +84,39 @@ .667,.667,.611,.278,.278,.278,.469,.556, .333,.556,.556,.500,.556,.556,.278,.556, // 60 .556,.222,.222,.500,.222,.833,.556,.556, .556,.556,.333,.500,.278,.556,.500,.722, // 70 .500,.500,.500,.334,.260,.334,.584,.512]), - - cw_tb = sw_tb // current width table +// monospace + mw_tb = new Float32Array([ + .0,.0,.0,.0,.0,.0,.0,.0, // 00 + .0,.0,.0,.0,.0,.0,.0,.0, + .0,.0,.0,.0,.0,.0,.0,.0, // 10 + .0,.0,.0,.0,.0,.0,.0,.0, + .52,.52,.52,.52,.52,.52,.52,.52, // 20 + .52,.52,.52,.52,.52,.52,.52,.52, + .52,.52,.52,.52,.52,.52,.52,.52, // 30 + .52,.52,.52,.52,.52,.52,.52,.52, + .52,.52,.52,.52,.52,.52,.52,.52, // 40 + .52,.52,.52,.52,.52,.52,.52,.52, + .52,.52,.52,.52,.52,.52,.52,.52, // 50 + .52,.52,.52,.52,.52,.52,.52,.52, + .52,.52,.52,.52,.52,.52,.52,.52, // 60 + .52,.52,.52,.52,.52,.52,.52,.52, + .52,.52,.52,.52,.52,.52,.52,.52, // 70 + .52,.52,.52,.52,.52,.52,.52,.52]) /* -- return the character width -- */ -function cwid(c) { +function cwid(c, font) { var i = c.charCodeAt(0) // utf-16 if (i >= 0x80) { // if not ASCII if (i >= 0x300 && i < 0x370) return 0; // combining diacritical mark i = 0x61 // 'a' } - return cw_tb[i] + return (font || gene.curfont).cw_tb[i] } // return the character width with the current font function cwidf(c) { return cwid(c) * gene.curfont.swfac } @@ -112,11 +128,11 @@ if (typeof document != "undefined") { // .. by the browser // create a text element if not done yet - var el = abc2svg.eltxt + var el // change the function strwh = function(str) { if (str.wh) return str.wh @@ -123,12 +139,12 @@ 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) - abc2svg.eltxt = el // reused after new Abc() } var c, font = gene.curfont, h = font.size, @@ -219,11 +235,11 @@ i = j; c = 'a' // XML character reference } break } - w += cwid(c) * swfac + w += cwid(c, font) * swfac } return [w, h] } } })() @@ -301,11 +317,10 @@ // set the default and current font function set_font(xxx) { if (typeof xxx == "string") xxx = get_font(xxx) - cw_tb = xxx.name.slice(0, 4) == 'sans' ? ssw_tb : sw_tb gene.curfont = gene.deffont = xxx } // output a string handling the font changes function out_str(str) { @@ -323,10 +338,26 @@ action, // default: align left w, // needed for justify wh) { // optional [width, height] if (!wh) wh = str.wh || strwh(str) + if (cfmt.trimsvg) { + var wx = wh[0] + switch (action) { + case 'c': + wx = wh[0] / 2 + break + case 'j': + wx = w + break + case 'r': + wx = 0 + break + } + if (img.wx < x + wx) + img.wx = x + wx + } output += '' break case 'j': output += '" textLength="' + w.toFixed(1) + '">' break case 'r': - x -= wh[0]; output += '" text-anchor="end">' break default: output += '">' break @@ -380,10 +409,12 @@ return title } // return the width of the music line function get_lwidth() { + if (img.chg) + set_page() return (img.width - img.lm - img.rm - 2) // for bar thickness at eol / cfmt.scale } @@ -403,11 +434,11 @@ h = cfmt.titlespace } wh = strwh(title) wh[1] += gene.curfont.pad * 2 vskip(wh[1] + h + gene.curfont.pad) - h = gene.curfont.pad + h = gene.curfont.pad + wh[1] * .22 // + descent if (cfmt.titleleft) xy_str(0, h, title, null, null, wh) else xy_str(get_lwidth() / 2, h, title, "c", null, wh) } @@ -673,19 +704,23 @@ head = names[i].slice(2) if (head[0] == '"') head = head.slice(1, -1); vskip(h); wh = strwh(head); - xy_str(0, 0, head, null, null, wh); + xy_str(0, wh[1] * .22, head, null, null, wh); w = wh[0]; str = str.split('\n'); - xy_str(w, 0, str[0]) + xy_str(w, wh[1] * .22, str[0]) for (j = 1; j < str.length; j++) { + if (!str[j]) { // new paragraph + vskip(gene.curfont.size * cfmt.parskipfac) + continue + } vskip(h); - xy_str(w, 0, str[j]) + xy_str(w, wh[1] * .22, str[j]) } - vskip(h * .3); + vskip(h * cfmt.parskipfac) use_font(gene.curfont) } } /* -- write heading with format -- */ @@ -927,20 +962,19 @@ i = j + 1 } } /* rhythm, composer, origin */ -// down1 = cfmt.composerspace + gene.curfont.size down1 = down2 = 0 if (parse.ckey.k_bagpipe && !cfmt.infoline && cfmt.writefields.indexOf('R') >= 0) rhythm = info.R if (rhythm) { set_font("composer"); - xy_str(0, -cfmt.composerspace, rhythm); - down1 = cfmt.composerspace + down1 = cfmt.composerspace + gene.curfont.size + 2 + xy_str(0, -down1 + gene.curfont.size *.22, rhythm) } area = info.A if (cfmt.writefields.indexOf('C') >= 0) composer = info.C if (cfmt.writefields.indexOf('O') >= 0) @@ -947,11 +981,10 @@ origin = info.O if (composer || origin || cfmt.infoline) { var xcomp, align; set_font("composer"); - vskip(cfmt.composerspace) if (cfmt.aligncomposer < 0) { xcomp = 0; align = ' ' } else if (cfmt.aligncomposer == 0) { xcomp = lwidth * .5; @@ -958,61 +991,55 @@ align = 'c' } else { xcomp = lwidth; align = 'r' } - down2 = down1 if (composer || origin) { - if (cfmt.aligncomposer >= 0 - && down1 != down2) - vskip(down1 - down2); + down2 = cfmt.composerspace + 2 i = 0 while (1) { - vskip(gene.curfont.size) + down2 += gene.curfont.size if (composer) j = composer.indexOf("\n", i) else j = -1 if (j < 0) { - put_inf2r(xcomp, 0, + put_inf2r(xcomp, -down2 + gene.curfont.size *.22, composer ? composer.substring(i) : null, origin, align) break } - xy_str(xcomp, 0, composer.slice(i, j), align); - down1 += gene.curfont.size; + xy_str(xcomp, -down2 + gene.curfont.size *.22, + composer.slice(i, j), align); i = j + 1 } - if (down2 > down1) - vskip(down2 - down1) } rhythm = rhythm ? null : info.R if ((rhythm || area) && cfmt.infoline) { /* if only one of rhythm or area then do not use ()'s * otherwise output 'rhythm (area)' */ set_font("info"); - vskip(gene.curfont.size + cfmt.infospace); - put_inf2r(lwidth, 0, rhythm, area, 'r'); - down1 += gene.curfont.size + cfmt.infospace + down2 += cfmt.infospace + gene.curfont.size + put_inf2r(lwidth, -down2 + gene.curfont.size *.22, + rhythm, area, 'r') } -// down2 = 0 - } else { - down2 = cfmt.composerspace } /* parts */ if (info.P && cfmt.writefields.indexOf('P') >= 0) { set_font("parts"); - down1 = cfmt.partsspace + gene.curfont.size - down1 - if (down1 > 0) - down2 += down1 - if (down2 > .01) - vskip(down2); - xy_str(0, 0, info.P); - down2 = 0 + 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) + 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-2022 Jean-Francois Moine +// Copyright (C) 2014-2023 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -30,12 +30,13 @@ posx = cfmt.leftmargin / cfmt.scale, // default x offset of the images posy = 0, // y offset in the block img = { // image width: cfmt.pagewidth, // width lm: cfmt.leftmargin, // left and right margins - rm: cfmt.rightmargin -// chg: false + rm: cfmt.rightmargin, + wx: 0, // used width between the left and right margins + chg: 1 //true }, defined_glyph = {}, defs = '', fulldefs = '', // unreferenced defs as stv_g = { /* staff/voice graphic parameters */ @@ -60,14 +61,14 @@ mphr: {x:0, y:23, c:"\ue038"}, sphr: {x:0, y:26, 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 - dsgn: {x:-4, y:-4, c:"\ue045"}, // D.S. - dcap: {x:-4, y:-4, c:"\ue046"}, // D.C. - sgno: {x:-6, y:0, c:"\ue047"}, // segno - coda: {x:-12, y:-6, c:"\ue048"}, + 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"}, cclef: {x:-8, y:0, c:"\ue05c"}, bclef: {x:-8, y:0, c:"\ue062"}, pclef: {x:-6, y:0, c:"\ue069"}, spclef: {x:-6, y:0, c:"\ue069"}, @@ -129,16 +130,16 @@ "acc-2": {x:-3, y:0, c:"\ue264"}, // double flat "acc-1_2": {x:-2, y:0, c:"\ue280"}, // quarter-tone flat "acc-3_2": {x:-3, y:0, c:"\ue281"}, // three-quarter-tones flat acc1_2: {x:-1, y:0, c:"\ue282"}, // quarter-tone sharp acc3_2: {x:-3, y:0, c:"\ue283"}, // three-quarter-tones sharp - accent: {x:-3, y:2.5, c:"\ue4a0"}, - stc: {x:-1, y:-2, c:"\ue4a2"}, // staccato - emb: {x:-4, y:-2, c:"\ue4a4"}, - wedge: {x:-1, y:0, c:"\ue4a8"}, + accent: {x:-3, y:2, c:"\ue4a0"}, + stc: {x:0, y:-2, c:"\ue4a2"}, // staccato + emb: {x:0, y:-2, c:"\ue4a4"}, + wedge: {x:0, y:0, c:"\ue4a8"}, marcato: {x:-3, y:-2, c:"\ue4ac"}, - hld: {x:-7, y:-2, c:"\ue4c0"}, // fermata + hld: {x:-7, y:-2, c:"\ue4c0"}, // fermata brth: {x:0, y:0, c:"\ue4ce"}, caes: {x:0, y:8, c:"\ue4d1"}, r00: {x:-1.5, y:0, c:"\ue4e1"}, r0: {x:-1.5, y:0, c:"\ue4e2"}, r1: {x:-3.5, y:-6, c:"\ue4e3"}, @@ -150,35 +151,35 @@ r64: {x:-4, y:0, c:"\ue4e9"}, r128: {x:-4, y:0, c:"\ue4ea"}, // mrest: {x:-10, y:0, c:"\ue4ee"}, mrep: {x:-6, y:0, c:"\ue500"}, mrep2: {x:-9, y:0, c:"\ue501"}, - p: {x:-4, y:-6, c:"\ue520"}, - f: {x:-2.5, y:-6, c:"\ue522"}, - pppp: {x:-15, y:-6, c:"\ue529"}, - ppp: {x:-11, y:-6, c:"\ue52a"}, - pp: {x:-8, y:-6, c:"\ue52b"}, - mp: {x:-8.5, y:-6, c:"\ue52c"}, - mf: {x:-8, y:-6, c:"\ue52d"}, - ff: {x:-6, y:-6, c:"\ue52f"}, - fff: {x:-9, y:-6, c:"\ue530"}, - ffff: {x:-12, y:-6, c:"\ue531"}, - sfz: {x:-9, y:-6, c:"\ue539"}, - trl: {x:-4, y:-2, c:"\ue566"}, // trill - turn: {x:-5, y:3, c:"\ue567"}, - turnx: {x:-5, y:3, c:"\ue569"}, - umrd: {x:-7, y:2, c:"\ue56c"}, - lmrd: {x:-7, y:2, c:"\ue56d"}, - dplus: {x:-4, y:0, c:"\ue582"}, // plus + p: {x:-3, y:0, c:"\ue520"}, + f: {x:-3, y:0, c:"\ue522"}, + pppp: {x:-15, y:0, c:"\ue529"}, + ppp: {x:-14, y:0, c:"\ue52a"}, + pp: {x:-8, y:0, c:"\ue52b"}, + mp: {x:-8, y:0, c:"\ue52c"}, + mf: {x:-8, y:0, c:"\ue52d"}, + ff: {x:-7, y:0, c:"\ue52f"}, + fff: {x:-10, y:0, c:"\ue530"}, + ffff: {x:-14, y:0, c:"\ue531"}, + sfz: {x:-10, y:0, c:"\ue539"}, + trl: {x:-5, y:-2, c:"\ue566"}, // trill + 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 - grm: {x:-2, y:0, c:"\ue5e2"}, // grace mark - dnb: {x:-4, y:0, c:"\ue610"}, // down bow - upb: {x:-3, y:0, c:"\ue612"}, // up bow - opend: {x:-2, y:0, c:"\ue614"}, // harmonic - roll: {x:0, y:2, c:"\ue618"}, - thumb: {x:0, y:0, c:"\ue624"}, - snap: {x:-2, y:0, c:"\ue630"}, + 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"}, + thumb: {x:-2, y:-2, c:"\ue624"}, + snap: {x:-2, y:-2, c:"\ue630"}, ped: {x:-10, y:0, c:"\ue650"}, pedoff: {x:-5, y:0, c:"\ue655"}, // "mtro.": {x:0, y:0, c:"\ue910"}, // (unused) mtro: {x:0, y:0, c:"\ue911"}, // tempus perfectum // "mtro|": {x:0, y:0, c:"\ue912"}, // (unused) @@ -883,57 +884,63 @@ x, y + 1, w) } // decorations with string var deco_str_style = { -crdc: { +crdc: { // cresc., decresc., dim., ... dx: 0, dy: 5, - style: 'font:italic 14px text,serif' + style: 'font:italic 14px text,serif', + anchor: ' text-anchor="middle"' }, -dacs: { +dacs: { // long repeats (da capo, fine...) dx: 0, dy: 3, - style: 'font:16px text,serif', + style: 'font:bold 15px text,serif', anchor: ' text-anchor="middle"' }, pf: { dx: 0, dy: 5, - style: 'font:italic bold 16px text,serif' - }, -'@': { - dx: 0, - dy: 5, - style: 'font:12px text,sans-serif' + style: 'font:italic bold 16px text,serif', + anchor: ' text-anchor="middle"' } } +deco_str_style.at = deco_str_style.crdc -function out_deco_str(x, y, name, str) { +function out_deco_str(x, y, de) { + var name = de.dd.glyph // class + if (name == 'fng') { out_XYAB('\ -A\n', - x, y, m_gl(str)) - return - } - var a, f, - a_deco = deco_str_style[name] - - if (!a_deco) { - xygl(x, y, name) - return - } - x += a_deco.dx; - y += a_deco.dy; - if (!a_deco.def) { - style += "\n." + name + " {" + a_deco.style + "}"; - a_deco.def = true - } +A\n', + x - 2, y, m_gl(de.dd.str)) + return + } + + if (name == '@') { // compatibility + name = 'at' + } else if (!/^[A-Za-z][A-Za-z\-_]*$/.test(name)) { + error(1, de.s, "No function for decoration '$1'", de.dd.name) + return + } + + var f, + a_deco = deco_str_style[name] + + if (!a_deco) + a_deco = deco_str_style.crdc // default style + else if (a_deco.style) + style += "\n." + name + "{" + a_deco.style + "}", + delete a_deco.style + + x += a_deco.dx; + y += a_deco.dy; out_XYAB('', x, y, name, a_deco.anchor || ""); set_font("annotation"); - out_str(str); + out_str(de.dd.str) output += '\n' } function out_arp(x, y, val) { g_open(x, y, 270); @@ -947,20 +954,20 @@ } function out_cresc(x, y, val, defl) { x += val * stv_g.scale val = -val; out_XYAB('\n' else output += '-4l' + (-val).toFixed(1) + ' -4"/>\n' } function out_dim(x, y, val, defl) { out_XYAB('\n' else output += '-4l' + (-val).toFixed(1) + ' -4"/>\n' } @@ -971,11 +978,10 @@ xygl(x, y, "ltr"); x += 6 } } Abc.prototype.out_lped = function(x, y, val, defl) { - y += 4; if (!defl.nost) xygl(x, y, "ped"); if (!defl.noen) xygl(x + val + 6, y, "pedoff") } @@ -1132,16 +1138,60 @@ glisq: out_glisq, gliss: out_gliss } function out_deco_long(x, y, de) { - var name = de.dd.glyph + var s, p_v, m, nt, i, + name = de.dd.glyph, + de1 = de.start - if (deco_l_tb[name]) - deco_l_tb[name](x, y, de) - else + if (!deco_l_tb[name]) { error(1, null, "No function for decoration '$1'", name) + return + } + + // if no start or no end, get the y offset of the other end + p_v = de.s.p_v // voice + if (de.defl.noen) { // if no end + s = p_v.s_next // start of the next music line + while (s && !s.dur) + s = s.next + if (s) { + for (m = 0; m <= s.nhd; m++) { + nt = s.notes[m] + if (!nt.a_dd) + continue + for (i = 0; i < nt.a_dd.length; i++) { + if (nt.a_dd[i].name == de.dd.name) { + y = 3 * (nt.pit - 18) + + staff_tb[de.s.st].y + break + } + } + } + } + x += 8 // (there is no note width) + } else if (de.defl.nost) { // no start + s = p_v.s_prev // end of the previous music line + while (s && !s.dur) + s = s.prev + if (s) { + for (m = 0; m <= s.nhd; m++) { + nt = s.notes[m] + if (!nt.a_dd) + continue + for (i = 0; i < nt.a_dd.length; i++) { + if (nt.a_dd[i].name == de1.dd.name) { + de1.y = 3 * (nt.pit - 18) + break + } + } + } + } + de1.x -= 8 // (there is no note width) + } + deco_l_tb[name](x, y, de) } // return a tempo note function tempo_note(s, dur) { var p, @@ -1282,10 +1332,14 @@ function svg_flush() { if (multicol || !output || !user.img_out || posy == 0) return var i, font, + fmt = tsnext ? tsnext.fmt : cfmt, + w = Math.ceil((fmt.trimsvg || fmt.singleline == 1) + ? (cfmt.leftmargin + img.wx * cfmt.scale + cfmt.rightmargin + 2) + : img.width), head = '\n' - } else { - head += ' viewBox="0 0 ' + img.width.toFixed(0) + ' ' + - posy.toFixed(0) + - '" width="' + img.width.toFixed(0) + - 'px" height="' + posy.toFixed(0) + 'px">\n' - } - - head += fulldefs - - if (style || font_style) { - head += '\n' - } + if (user.imagesize != undefined) + 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 + + if (style || font_style) + head += '\n' if (defs) head += '' + defs + '\n\n' // if %%pagescale != 1, do a global scale @@ -1350,18 +1396,19 @@ output = "" font_style = '' if (cfmt.fullsvg) { defined_glyph = {} - for (i = 0; i < font_tb.length; i++) - font_tb[i].used = false + for (i = 0; i < abc2svg.font_tb.length; i++) + abc2svg.font_tb[i].used = false } else { style = ''; fulldefs = '' } defs = ''; posy = 0 + img.wx = 0 // space used between the margins } // mark the end of a
block function blk_flush() { svg_flush() Index: core/tail.js ================================================================== --- core/tail.js +++ core/tail.js @@ -1,8 +1,8 @@ // abc2svg - tail.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 @@ -21,10 +21,11 @@ init_tune() // Abc functions used by the modules 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.deco_put = function(nm, s) { a_dcn.push(nm) deco_cnv(s) @@ -76,10 +77,11 @@ Abc.prototype.set_cur_sy = function(sy) { cur_sy = sy }; Abc.prototype.set_dscale = set_dscale; Abc.prototype.set_font = set_font; Abc.prototype.set_a_gch = function(s, a) { a_gch = a; csan_add(s) } Abc.prototype.set_hl = set_hl +Abc.prototype.set_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 Abc.prototype.set_tsfirst = function(s) { tsfirst = s }; @@ -92,38 +94,12 @@ Abc.prototype.unlksym = unlksym; Abc.prototype.use_font = use_font; Abc.prototype.vskip = vskip Abc.prototype.xy_str = xy_str; Abc.prototype.xygl = xygl; - - var hook_init // set after setting the first module hooks - - // export functions and/or set module hooks - function set_hooks() { - var h = abc2svg.modules.hooks, - gh = abc2svg.modules.g_hooks - - function set_hs(hs) { - for (var k = 0; k < hs.length; k++) - hs[k](self) - } // set_hs() - - if (hook_init) { // if new modules - if (h.length) { - set_hs(h); - gh.push.apply(gh, h); - abc2svg.modules.hooks = [] - } - } else { // all modules - if (h.length) { - gh.push.apply(gh, h); - abc2svg.modules.hooks = [] - } - set_hs(gh); - hook_init = true - } - } // set_hooks() +Abc.prototype.y_get = y_get +Abc.prototype.y_set = y_set } // end of Abc() // compatibility var Abc = abc2svg.Abc Index: core/tune.js ================================================================== --- core/tune.js +++ core/tune.js @@ -1,8 +1,8 @@ // abc2svg - tune.js - tune generation // -// 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 @@ -67,10 +67,12 @@ s.prev = curvoice.last_sym if (curvoice.last_sym) curvoice.last_sym.next = s else curvoice.sym = s + } else if (s.bar_type) { + curvoice.last_bar = s } curvoice.last_sym = s s.v = curvoice.v; s.p_v = curvoice; s.st = curvoice.cst; @@ -114,112 +116,148 @@ } /* -- sort all symbols by time and vertical sequence -- */ // weight of the symbols !! depends on the symbol type !! var w_tb = new Uint8Array([ - 5, // bar - 1, // clef + 6, // bar + 2, // clef 8, // custos 6, // sm (sequence marker, after bar) 0, // grace (must be null) - 2, // key - 3, // meter + 3, // key + 4, // meter 9, // mrest 9, // note 0, // part 9, // rest - 4, // space (before bar) + 5, // space (before bar) 0, // staves - 7, // stbrk + 1, // stbrk 0, // tempo 0, // (free) 0, // block 0 // remark ]) function sort_all() { - var s, s2, time, w, wmin, ir, fmt, + var s, s2, time, w, wmin, ir, fmt, v, p_voice, prev, fl, new_sy, nv = voice_tb.length, vtb = [], vn = [], // voice indexed by range - sy = cur_sy, // first staff system - v = sy.top_voice, - p_voice = voice_tb[v], // top voice - prev = { // symbol defining the first staff system - type: C.STAVES, - dur: 0, - v: v, - p_v: p_voice, - time: 0, - st: 0, - sy: sy, - next: p_voice.sym, - seqst: true - } - - if (!p_voice.sym) - return + sy = cur_sy // first staff system // check if different bars at the same time function b_chk() { - var ir, bt, s, s2, v - + var bt, s, s2, v, t, ir = 0 - bt = "" + while (1) { v = vn[ir++] if (v == undefined) break s = vtb[v] - if (!s || !s.bar_type || s.invis) + if (!s || !s.bar_type || s.invis + || s.time != time) continue if (!bt) { bt = s.bar_type + if (s.text && bt == '|') + t = s.text continue } if (s.bar_type != bt) break + if (s.text && !t && bt == '|') { + t = s.text + break + } } - if (!v) + + // 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) { + vtb[prev.v] = prev + prev = prev.ts_prev + fl = 1 //true + } + } + + if (v == undefined) return // no problem - - if (bt == "::" || bt == ":|") { + // change "::" to ":| |:" + // and "|1" to "| [1" + if (bt == "::" || bt == ":|" + || t) { ir = 0 + bt = t ? '|' : "::" while (1) { v = vn[ir++] if (v == undefined) break s = vtb[v] if (!s || s.invis - || s.bar_type != "::") + || s.bar_type != bt + || (bt == '|' && !s.text)) continue s2 = clone(s) - s.bar_type = ":|" - s2.bar_type = "|:" + if (bt == "::") { + s.bar_type = ":|" + s2.bar_type = "|:" + } else { +// s.bar_type = '|' + delete s.text + delete s.rbstart + s2.bar_type = '[' + s2.invis = 1 //true + s2.xsh = 0 + } s2.next = s.next + if (s2.next) + s2.next.prev = s2 s2.prev = s s.next = s2 } } else { - error(2, s, "Different bars $1 and $2", - bt, s.bar_type) + error(1, s, "Different bars $1 and $2", + (bt + (t || '')), (s.bar_type + (s.text || ''))) } } // b_chk() - prev.fmt = fmt = p_voice.sym.fmt // starting format - // set the first symbol of each voice for (v = 0; v < nv; v++) { s = voice_tb[v].sym vtb[v] = s - if (sy.voices[v]) + if (sy.voices[v]) { vn[sy.voices[v].range] = v + if (!prev && s) { + fmt = s.fmt + p_voice = voice_tb[v] + prev = { // symbol defining the first staff system + type: C.STAVES, + fname: parse.fname, + dur: 0, + v: v, + p_v: p_voice, + time: 0, + st: 0, + sy: sy, + next: s, + fmt: fmt, + seqst: true + } + } + } } - // insert the first staff system in the top voice + if (!prev) + return // no symbol yet + + // insert the first staff system in the first voice p_voice.sym = tsfirst = s = prev if (s.next) s.next.prev = s else p_voice.last_sym = s @@ -250,16 +288,29 @@ s.ts_next = s.next while (1) { s = s.next if (!s) return - s.ts_prev = s.prev - s.ts_next = s.next if (s.time != s.prev.time || w_tb[s.prev.type] || s.type == C.GRACE && s.prev.type == C.GRACE) s.seqst = 1 //true + if (s.type == C.PART) { // move the part + s.prev.next = + s.prev.ts_next = s.next + if (s.next) { + s.next.part = s // to the next symbol + s.next.prev = s.prev + if (s.soln) + s.next.soln = 1 //true + if (s.seqst) + s.next.seqst = 1 //true + } + continue + } + s.ts_prev = s.prev + s.ts_next = s.next } // not reached } // loop on the symbols of all voices @@ -298,28 +349,46 @@ if (wmin > 127) break // done // check the type of the measure bars - if (wmin == 5) // weight of bars + if (wmin == 6) // !! weight of bars b_chk() /* link the vertical sequence */ ir = 0 while (1) { v = vn[ir++] if (v == undefined) break s = vtb[v] - if (!s) + if (!s + || s.time != time) continue w = w_tb[s.type] - if (s.type == C.GRACE && s.next && s.next.type == C.GRACE) + if (!w + && s.type == C.GRACE && s.next && s.next.type == C.GRACE) w-- - if (s.time != time - || w != wmin) + if (w != wmin) + continue + if (!w + && s.type == C.PART) { // move the part + if (s.prev) + s.prev.next = s.next + else + s.p_v.sym = s.next + vtb[v] = s.next + if (s.next) { + s.next.part = s // to the next symbol + s.next.prev = s.prev + if (s.soln) + s.next.soln = 1 //true +// } else { +// ignored + } continue + } if (s.type == C.STAVES) new_sy = s.sy if (fl) { fl = 0; s.seqst = true @@ -328,17 +397,54 @@ prev.ts_next = s prev = s vtb[v] = s.next } - fl = wmin /* start a new sequence if some width */ + if (wmin) // if some width + fl = 1 //true // start a new sequence } } // adjust some voice elements -function voice_adj(sys_chg) { +// (possible hook) +Abc.prototype.voice_adj = function (sys_chg) { var p_voice, s, s2, v, sl + + // insert the delayed P: and Q: in the top_voice + function ins_pq() { + var s, s2, + p_v = voice_tb[par_sy.top_voice] + + while (1) { + s = parse.pq_d.shift() + if (!s) + break + for (s2 = p_v.sym; ; s2 = s2.next) { + if (s2.time >= s.time + && s2.dur) { + s.next = s2 + s.prev = s2.prev + s.prev.next = + s2.prev = s + s.v = s2.v + s.p_v = p_v + s.st = s2.st + break + } + } + } + } // 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, @@ -385,10 +491,18 @@ if (curvoice && curvoice.clone) { parse.istart = parse.eol do_cloning() } + // if only one voice and a time skip, + // fill the voice with the sequence "Z |" (multi-rest and bar) + if (par_sy.one_v) // if one voice + fill_mr_ba(voice_tb[par_sy.top_voice]) + + if (parse.pq_d) + ins_pq() // insert delayed P: and Q: + for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] if (!sys_chg) { // if not %%score delete p_voice.eoln while (1) { // set the end of slurs @@ -453,14 +567,18 @@ break } break } continue + case C.NOTE: + if (s.feathered_beam) + set_feathered_beam(s) + if (p_voice.map + && maps[p_voice.map]) + do_map(s) + break } - - if (s.feathered_beam) - set_feathered_beam(s) } } } /* -- create a new staff system -- */ @@ -544,11 +662,12 @@ break } // at start of tune, check for an anacrusis for (s2 = s.ts_next; s2; s2 = s2.ts_next) { - if (s2.type == C.BAR && s2.time) { + if (s2.type == C.BAR && s2.time + && !s2.invis && !s2.bar_dotted) { if (s2.time < wmeasure) { // if anacrusis s = s2 bar_tim = s.time } break @@ -557,26 +676,26 @@ // set the measure number on the top bars for ( ; s; s = s.ts_next) { switch (s.type) { case C.METER: - if (s.time == bar_tim) - break // already seen - if (wmeasure != 1) // if not M:none - bar_num += (s.time - bar_tim) / wmeasure + if (wmeasure != 1) // if not M:none + bar_num += (s.time - bar_tim) / wmeasure bar_tim = s.time wmeasure = s.wmeasure + while (s.ts_next && s.ts_next.wmeasure) + s = s.ts_next break case C.BAR: if (s.time <= tim) break // already seen tim = s.time nu = 1 //true // no num update txt = "" for (s2 = s; s2; s2 = s2.next) { - if (s2.time > tim || s2.dur) + if (s2.time > tim) break if (!s2.bar_type) continue if (s2.bar_type != '[') nu = 0 //false // do update @@ -584,15 +703,15 @@ txt = s2.text } if (s.bar_num) { bar_num = s.bar_num // (%%setbarnb) ptim = bar_tim = tim - continue + break } if (wmeasure == 1) { // if M:none if (s.bar_dotted) - continue + break if (txt) { if (!cfmt.contbarnb) { if (txt[0] == '1') rep_tim = bar_num else @@ -599,11 +718,11 @@ bar_num = rep_tim } } if (!nu) s.bar_num = ++bar_num - continue + break } n = bar_num + (tim - bar_tim) / wmeasure k = n - (n | 0) if (cfmt.checkbars @@ -617,27 +736,22 @@ bar_num = n } if (txt) { if (txt[0] == '1') { - if (cfmt.contbarnb) - rep_tim = bar_tim + k * wmeasure - else - rep_tim = tim + if (!cfmt.contbarnb) + rep_tim = tim - bar_tim if (!nu) s.bar_num = n } else { - if (cfmt.contbarnb) - bar_tim = rep_tim - else - bar_tim += tim - rep_tim + if (!cfmt.contbarnb) + bar_tim = tim - rep_tim n = bar_num + (tim - bar_tim) / wmeasure if (n == (n | 0)) s.bar_num = n } - } else { - n |= 0 + } else if (n == (n | 0)) { s.bar_num = n } if (!k) ptim = tim break @@ -681,11 +795,11 @@ var i, note, notes, map, tmp, ns, ty = '', a = text.split(/\s+/) if (a.length < 3) { - syntax(1, not_enough_p) + syntax(1, errs.not_enough_p) return } ns = a[1] if (ns[0] == '*' || ns.indexOf("all") == 0) { ns = 'all' @@ -739,100 +853,43 @@ for (; i < a.length; i++) { switch (a[i]) { case "heads=": if (!a[++i]) { - syntax(1, not_enough_p) + syntax(1, errs.not_enough_p) break } map[0] = a[i].split(',') break case "print=": case "play=": + case "print_notrp=": if (!a[++i]) { - syntax(1, not_enough_p) + syntax(1, errs.not_enough_p) break } tmp = new scanBuf; tmp.buffer = a[i]; note = parse_acc_pit(tmp) + if (a[i - 1][5] == '_') // if print no transpose + note.notrp = 1 //true if (a[i - 1][1] == 'r') map[1] = note else map[3] = note break case "color=": if (!a[++i]) { - syntax(1, not_enough_p) + syntax(1, errs.not_enough_p) break } map[2] = a[i] break } } } -// set the transposition in the first key signature or in a new key -function set_transp() { - var s - - if (curvoice.ckey.k_bagpipe || curvoice.ckey.k_drum) - return - - if (cfmt.transp && curvoice.shift) // if %%transpose and shift= - syntax(0, "Mix of old and new transposition syntaxes"); - - if (is_voice_sig()) { // if no symbol yet - curvoice.okey.fmt = cfmt - curvoice.key = s = clone(curvoice.okey) - } else { - s = clone(curvoice.ckey) - s.k_old_sf = s.k_sf - sym_link(s) - } - delete s.invis // needed if K:C/none at start of tune - key_transp(s) - curvoice.ckey = clone(s) - if (curvoice.key.k_none) - s.k_sf = 0 -} - -// set the control values (P: and Q:) -function set_ctrl() { - var s, tim, e - - for (tim in parse.ctrl) { -// if (!parse.ctrl.hasOwnProperty(tim)) -// continue - e = parse.ctrl[tim] - s = tsfirst - while (s.next && s.next.time < tim) - s = s.next - while (s && s.time < tim) - s = s.ts_next - if (!s) { - //fixme: insert at the end - } else { - - // put the P: and/or Q: on a note or rest - while (s.time == tim && s.ts_next - && !s.dur) - s = s.ts_next - if (e.part) { -// if (s.time != tim) { -// // fixme: insert a part (?) -// } - s.part = e.part - } - if (e.tempo) { - lkvsym(e.tempo, s) - lktsym(e.tempo, s) - } - } - } -} // set_ctrl() - // get a abcm2ps/abcMIDI compatible transposition value as a base-40 interval // The value may be // - [+|-][s|f] // - [] % default is 'c' function get_transp(param) { @@ -844,14 +901,14 @@ //fixme: no source reference... syntax(1, errs.bad_transp) return } val += 36 - return (((val / 12) | 0) - 3) * 40 + - (param.slice(-1) == 'b' ? - abc2svg.ifb40 : - abc2svg.isb40)[val % 12] + val = ((val / 12 | 0) - 3) * 40 + abc2svg.isb40[val % 12] + if (param.slice(-1) == 'b') + val += 4 + return val } // return undefined } // get_transp() /* -- process a pseudo-comment (%% or I:) -- */ @@ -914,54 +971,51 @@ val return } break case "multicol": - if (parse.state >= 2) { - curvoice = voice_tb[0] - s = new_block("mc_" + param) - break - } switch (param) { case "start": - self.block_gen({ - subtype: "mc_start" - }) - break case "new": - if (!multicol) { - syntax(1, "%%multicol new without start") - break - } - self.block_gen({ - subtype: "mc_new" - }) - break case "end": - if (!multicol) { - syntax(1, "%%multicol end without start") - break - } - self.block_gen({ - subtype: "mc_end" - }) break default: syntax(1, "Unknown keyword '$1' in %%multicol", param) - break + return + } + s = { + type: C.BLOCK, + subtype: "mc_" + param, + dur: 0 + } + if (parse.state >= 2) { + curvoice = voice_tb[0] + curvoice.eoln = 1 //true + sym_link(s) + return } + set_ref(s) + self.block_gen(s) return case "ottava": if (parse.state != 3) return n = parseInt(param) - if (isNaN(n) || n < -2 || n > 2) { + if (isNaN(n) || n < -2 || n > 2 + || (!n && !curvoice.ottava)) { syntax(1, errs.bad_val, "%%ottava") return } - glovar.ottava = true; - parse.ottava.push(n) + k = n + if (n) { + curvoice.ottava = n + } else { + n = curvoice.ottava + curvoice.ottava = 0 + } + a_dcn.push(["15mb", "8vb", "", "8va", "15ma"][n + 2] + + (k ? '(' : ')')) return case "repbra": if (curvoice) curvoice.norepbra = !get_bool(param) return @@ -1087,10 +1141,14 @@ } else { s.xmx = 14 } sym_link(s) return + case "tacet": + if (param[0] == '"') + param = param.slice(1, -1) + // fall thru case "stafflines": case "staffscale": case "staffnonote": set_v_param(cmd, param) return @@ -1134,12 +1192,12 @@ // fall thru case 1: cfmt.transp = (cfmt.transp || 0) + val return } - curvoice.tr_sco = val - set_transp() + curvoice.shift = val + key_trans() return case "tune": //fixme: to do return case "user": @@ -1264,11 +1322,11 @@ /* -- generate a piece of tune -- */ function generate() { var s, v, p_voice; if (a_dcn.length) { - syntax(1, "Decoration without symbol") + syntax(1, "Decoration(s) without symbol: $1", a_dcn) a_dcn = [] } if (parse.tp) { syntax(1, "No end of tuplet") @@ -1281,26 +1339,27 @@ if (vover) { syntax(1, "No end of voice overlay"); get_vover(vover.bar ? '|' : ')') } - voice_adj(); + self.voice_adj() sort_all() /* define the time / vertical sequences */ if (tsfirst) { + for (v = 0; v < voice_tb.length; v++) { + if (!voice_tb[v].key) + voice_tb[v].key = parse.ckey // set the starting key + } if (user.anno_start) anno_start = a_start if (user.anno_stop) anno_stop = a_stop self.set_bar_num() if (info.P) tsfirst.parts = info.P // for play - if (parse.ctrl) - set_ctrl() // set control values - // give the parser result to the application if (user.get_abcmodel) user.get_abcmodel(tsfirst, voice_tb, abc2svg.sym_name, info) if (user.img_out) // if SVG generation @@ -1319,56 +1378,151 @@ tunes.push([tsfirst, voice_tb, info, cfmt]) tsfirst = null } } -// transpose a key -function key_transp(s) { - if (s.k_none) // no key +// transpose the current key of the voice (called on K: or V:) +function key_trans() { + var i, n, a_acc, b40, d, + s = curvoice.ckey, // current key + ti = s.time || 0 + + if (s.k_bagpipe || s.k_drum) + return // no transposition + + // set the score transposition + n = (curvoice.score | 0) // new transposition + + (curvoice.shift | 0) + + (cfmt.transp | 0) + if ((curvoice.tr_sco | 0) == n) { // if same transposition + s.k_sf = curvoice.ckey.k_sf return - - var n, a_acc, - b40 = (s.k_b40 + 200 + curvoice.tr_sco) % 40, - d = abc2svg.b40k[b40] - b40 - - if (d) { - if (curvoice.tr_sco > 0) - curvoice.tr_sco -= d - else - curvoice.tr_sco += d - b40 += d + } + + // get the current key or create a new one + if (is_voice_sig()) { // if no symbol yet + curvoice.key = s // new root key of the voice + } else if (curvoice.time != ti) { // if no K: at this time + s = clone(s.orig || s) // new key + if (!curvoice.new) + s.k_old_sf = curvoice.ckey.k_sf + sym_link(s) + } + curvoice.ckey = s // current key + + if (cfmt.transp && curvoice.shift) // if %%transpose and shift= + syntax(0, "Mix of old and new transposition syntaxes"); + + // define the new key + curvoice.tr_sco = n // b40 interval + + n = abc2svg.b40l5[(n + 202) % 40] // transpose in the line of fifth + + s.orig.k_sf // + old = new sf + if (n < -7) { + n += 12 + curvoice.tr_sco -= 4 + } else if (n > 7) { + n -= 12 + curvoice.tr_sco += 4 + } + if (!s.k_none) + s.k_sf = n + for (b40 = 0; b40 < 40; b40++) { + if (abc2svg.b40l5[b40] == n) + break } s.k_b40 = b40 - s.k_sf = abc2svg.b40sf[b40] // transpose the accidental list if (!s.k_a_acc) return + d = b40 - s.orig.k_b40 a_acc = [] - for (n = 0; n < s.k_a_acc.length; n++) { - b40 = abc2svg.pab40(s.k_a_acc[n].pit, s.k_a_acc[n].acc) - + curvoice.tr_sco - - a_acc[n] = { + for (i = 0; i < s.k_a_acc.length; i++) { + b40 = abc2svg.pab40(s.k_a_acc[i].pit, s.k_a_acc[i].acc) + d + a_acc[i] = { pit: abc2svg.b40p(b40), acc: abc2svg.b40a(b40) || 3 } } s.k_a_acc = a_acc } + +// fill a voice with a multi-rest and a bar +function fill_mr_ba(p_v) { + var v, p_v2, + mxt = 0 + + for (v = 0; v < voice_tb.length; v++) { + if (voice_tb[v].time > mxt) { + p_v2 = voice_tb[v] + mxt = p_v2.time + } + } + if (p_v.time >= mxt) + return + + var p_v_sav = curvoice, + dur = mxt - p_v.time, + s = { + type: C.MREST, + stem: 0, + multi: 0, + nhd: 0, + xmx: 0, + frm: 1, //true // full measure rest + dur: dur, + dur_orig: dur, + nmes: dur / p_v.wmeasure, + notes: [{ + pit: 18, + dur: dur + }], + tacet: p_v.tacet + }, + s2 = { + type: C.BAR, + bar_type: '|', + dur: 0, + multi: 0 + } + + if (p_v2.last_sym.bar_type) + s2.bar_type = p_v2.last_sym.bar_type +// s2.soln = p_v2.last_sym.soln + + glovar.mrest_p = 1 //true + + curvoice = p_v + sym_link(s) + sym_link(s2) + + 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, 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 + do_cloning() + parse.eol = i + } if (parm) { a_vf = parse_staves(parm) // => array of [vid, flags] if (!a_vf) return + } else if (staves_found < 0) { + syntax(1, errs.bad_val, '%%' + cmd) + return } /* create a new staff system */ for (v = 0; v < nv; v++) { p_voice = voice_tb[v] @@ -1377,13 +1531,12 @@ } if (!maxtime) { // if first %%staves par_sy.staves = [] par_sy.voices = [] } else { - - if (nv) // if many voices - voice_adj(true) +// if (nv) // if many voices + self.voice_adj(1) /* * create a new staff system and * link the 'staves' symbol in a voice which is seen from * the previous system - see sort_all */ @@ -1423,12 +1576,20 @@ /* initialize the (old) voices */ for (v = 0; v < nv; v++) { p_voice = voice_tb[v] delete p_voice.second - delete p_voice.ignore delete p_voice.floating + if (p_voice.ignore) { + p_voice.ignore = 0 //false + s = p_voice.sym + if (s) { + while (s.next) + s = s.next + } + p_voice.last_sym = s // set back the last symbol + } } range = 0 for (i = 0; i < a_vf.length; i++) { vid = a_vf[i][0]; p_voice = new_voice(vid); @@ -1447,10 +1608,12 @@ break v = p_voice.v } } par_sy.top_voice = a_vf[0][0].v + if (a_vf.length == 1) + par_sy.one_v = 1 //true // one voice /* change the behavior from %%staves to %%score */ if (cmd[1] == 't') { /* if %%staves */ for (i = 0; i < a_vf.length; i++) { flags = a_vf[i][1] @@ -1560,24 +1723,23 @@ continue // set the staff of the overlay voices p_voice2 = p_voice.voice_down while (p_voice2) { + p_voice2.second = 1 //true i = p_voice2.v p_voice2.st = p_voice2.cst = par_sy.voices[i].st = st p_voice2 = p_voice2.voice_down } par_sy.voices[v].second = p_voice.second; st = p_voice.st - if (st > 0 && !p_voice.norepbra + if (st > 0 && p_voice.norepbra == undefined && !(par_sy.staves[st - 1].flags & STOP_BAR)) p_voice.norepbra = true } - if (!maxtime && nv) // if first staff system and many voices - voice_adj(true) curvoice = parse.state >= 2 ? voice_tb[par_sy.top_voice] : null } // get a voice or create a clone of the current voice @@ -1710,11 +1872,11 @@ p_voice2.acc = [] // no accidental if (!vover) { /* first '&' in a measure */ time = p_voice2.time if (curvoice.ignore) - s = null + s = curvoice.last_bar else for (s = curvoice.last_sym; /*s*/; s = s.prev) { if (s.type == C.BAR || s.time <= time) /* (if start of tune) */ break @@ -1750,107 +1912,131 @@ return true } // treat a clef found in the tune body function get_clef(s) { + var s2, s3 + + // special case for percussion + if (s.clef_type == 'p') { // if percussion clef + s2 = curvoice.ckey + s2.k_drum = 1 //true + s2.k_sf = 0 + s2.k_b40 = 2 + s2.k_map = abc2svg.keys[7] + if (!curvoice.key) + curvoice.key = s2 // new root key + } + if (!curvoice.time // (force a clef when new voice) && is_voice_sig()) { curvoice.clef = s s.fmt = cfmt return } - // clef change - sym_link(s); - s.clef_small = true - - // move the clef before a (not right repeat) bar - var s2 = s.prev - if (!s.invis // if not 'K: clef=none' - && s2 && s2.type == C.BAR - && s2.bar_type[0] != ':') { - s.next = s2 - s.prev = s2.prev - if (s.prev) - s.prev.next = s - s2.prev = s - s2.next = null + // if not clef=none, + // move the clef before a key and/or a (not right repeat) bar + if (s.clef_none) + s2 = null + else + for (s2 = curvoice.last_sym; + s2 && s2.time == curvoice.time; + s2 = s2.prev) { + if (w_tb[s2.type]) + break + } + if (s2 + && s2.time == curvoice.time // if no time skip + && s2.k_sf != undefined) { + s3 = s2 // move before a key signature + s2 = s2.prev + } + if (s2 + && s2.time == curvoice.time + && s2.bar_type && s2.bar_type[0] != ':') + s3 = s2 // move before a measure bar + if (s3) { + s2 = curvoice.last_sym + curvoice.last_sym = s3.prev + sym_link(s) + s.next = s3 + s3.prev = s curvoice.last_sym = s2 if (s.soln) { delete s.soln curvoice.eoln = true } + } else { + sym_link(s) } + + if (s.prev) // if not the first clef of the voice + s.clef_small = 1 //true // have a small clef } // treat K: (kp = key signature + parameters) function get_key(parm) { - var v, p_voice, transp, sndtran, tr_p, nt, + var v, p_voice, // [s_key, a] = new_key(parm) // KO with nodejs a = new_key(parm), s_key = a[0], - s = s_key + s = s_key, + empty = s.k_sf == undefined && !s.k_a_acc a = a[1] - if (parse.state == 1) { // in tune header (first K:) - if (s_key.k_sf == undefined && !s_key.k_a_acc) { // empty K: + if (empty) + s.invis = 1 //true // don't display empty K: + else + s.orig = s // new transposition base + + if (parse.state == 1) { // in tune header (first K:) + parse.ckey = s // root key + if (empty) { s_key.k_sf = 0; s_key.k_none = true s_key.k_map = abc2svg.keys[7] } for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v]; - p_voice.key = clone(s_key); - p_voice.okey = clone(s_key); p_voice.ckey = clone(s_key) } - parse.ckey = s_key + if (a.length) { + memo_kv_parm('*', a) + a = [] + } if (!glovar.ulen) glovar.ulen = C.BLEN / 8; - if (a.length) { - memo_kv_parm('*', a) - a = '' - } goto_tune() - } else { - set_kv_parm(a) - } - - tr_p = curvoice.tr_p // transposition change 1=sco, 2=snd - curvoice.tr_p = 0 - if (s.k_sf == undefined) { // if no key - if (!s.k_a_acc // if no accidental list - && !(tr_p & 1)) // and no score transposition - return // not a key signature - s_key.k_sf = curvoice.okey.k_sf // set the same key - s_key.k_b40 = curvoice.okey.k_b40 - } - - - if (curvoice.tr_sco) // if transpose score - key_transp(s) - curvoice.okey = clone(s_key) - - s_key.k_old_sf = curvoice.ckey.k_sf // memorize the key changes - - if ((!s_key.k_a_acc || !s_key.k_a_acc.length) - && !s_key.k_sf && !s_key.k_old_sf) - s_key.invis = true // don't display the key signature - - if (!s_key.k_b40) - s_key.k_b40 = curvoice.ckey.k_b40 - - curvoice.ckey = s_key - if (is_voice_sig()) { - s_key.fmt = cfmt - curvoice.key = clone(s_key) - if (s_key.k_none) - curvoice.key.k_sf = 0 - } else { - sym_link(s_key) // (don't move the key) - } + } else if (!empty) { + if (curvoice.tr_sco) + curvoice.tr_sco = undefined + s.k_old_sf = curvoice.ckey.k_sf // memorize the previous key + curvoice.ckey = s + sym_link(s) + } + + // set the voice parameters + if (!curvoice) { // if first K: + if (!voice_tb.length) { + curvoice = new_voice("1") + var def = 1 // true + } else { + curvoice = voice_tb[staves_found < 0 ? 0 : par_sy.top_voice] + } + } + + p_voice = curvoice.clone + if (p_voice) + curvoice.clone = null // don't stop the multi-voice sequence + get_voice(curvoice.id + ' ' + a.join(' ')) + if (p_voice) + curvoice.clone = p_voice + + if (def) + curvoice.default = 1 //true } // get / create a new voice function new_voice(id) { var v, p_v_sav, @@ -1861,19 +2047,11 @@ if (n == 1 && p_voice.default) { delete p_voice.default if (!p_voice.time) { // if no symbol yet p_voice.id = id - delete p_voice.init // reload the global attributes - if (cfmt.transp // != undefined - && parse.state >= 2) { - p_v_sav = curvoice; - curvoice = p_voice; - curvoice.tr_sco = cfmt.transp - set_transp(); - curvoice = p_v_sav - } + p_voice.init = 0 // set back the global voice parameters return p_voice // default voice } } for (v = 0; v < n; v++) { p_voice = voice_tb[v] @@ -1899,13 +2077,12 @@ scale: 1, // st: 0, // cst: 0, ulen: glovar.ulen, dur_fact: 1, - key: clone(parse.ckey), // key at start of tune (parse / gene) - ckey: clone(parse.ckey), // current key (parse / gene) - okey: clone(parse.ckey), // key without transposition (parse) +// key: clone(parse.ckey), // key at start of tune (parse / gene) +// ckey: clone(parse.ckey), // current key (parse / gene) meter: clone(glovar.meter), wmeasure: glovar.meter.wmeasure, staffnonote: 1, clef: { type: C.CLEF, @@ -1918,10 +2095,21 @@ hy_st: 0 } voice_tb.push(p_voice); + if (parse.state == 3) { +// p_voice.key = parse.ckey // (done later in music.js) + p_voice.ckey = clone(parse.ckey) + if (p_voice.ckey.k_bagpipe + && !p_voice.pos.stm) { + p_voice.pos = clone(p_voice.pos) + p_voice.pos.stm &= ~0x07 + p_voice.pos.stm |= C.SL_BELOW + } + } + // par_sy.voices[v] = { // range: -1 // } return p_voice @@ -1944,11 +2132,11 @@ var i, clone = curvoice.clone, vs = clone.vs, a = clone.a, bol = clone.bol, - eol = parse.istart, + eol = parse.eol, parse_sav = parse, file = parse.file delete curvoice.clone @@ -1958,11 +2146,11 @@ // insert the music sequence in each voice include++; for (i = 0; i < vs.length; i++) { parse = Object.create(parse_sav) // create a new parse context parse.line = Object.create(parse_sav.line) - get_voice(vs[i], a) + get_voice(vs[i] + ' ' + a.join(' ')) tosvg(parse.fname, file, bol, eol) } include-- parse = parse_sav // restore the parse context } @@ -2008,22 +2196,20 @@ // if many voices, memorize the start of sequence if (vs.length > 1) { vs.shift() curvoice.clone = { vs: vs, - a: a, + a: a.slice(0), // copy the parameters bol: parse.iend } if (parse.file[curvoice.clone.bol - 1] != ']') curvoice.clone.bol++ // start of new line } set_kv_parm(a) - if (curvoice.tr_p) { - curvoice.tr_p = 0 - set_transp() - } + + key_trans() v = curvoice.v if (curvoice.new) { // if new voice delete curvoice.new if (staves_found < 0) { // if no %%score/%%staves @@ -2066,26 +2252,15 @@ gene.nbar = 1 } parse.state = 3 // in tune body - // if no voice yet, create the default voice - if (!voice_tb.length) { - get_voice("1"); - curvoice.clef.istart = curvoice.key.istart; - curvoice.clef.iend = curvoice.key.iend; -// nstaff = 0; - curvoice.default = true - } else { - curvoice = voice_tb[staves_found < 0 ? 0 : par_sy.top_voice] - } - // update some voice parameters for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v]; p_voice.ulen = glovar.ulen - if (p_voice.ckey.k_bagpipe + if (parse.ckey.k_bagpipe && !p_voice.pos.stm) { p_voice.pos = clone(p_voice.pos) p_voice.pos.stm &= ~0x07 p_voice.pos.stm |= C.SL_BELOW } Index: dist_bin ================================================================== --- dist_bin +++ dist_bin @@ -3,10 +3,11 @@ MIDI-1.js Scc1t2/ abc2svg-1.js abc2svg.ttf abc2svg.woff +abc2svg.woff2 abcdoc-1.js abcjsc abcmjs abcnode abcqjs 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-2022 Jean-Francois Moine +// Copyright (C) 2014-2023 Jean-Francois Moine // // This file is part of abc2svg. // // abc2svg is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -26,10 +26,16 @@ alert("window error: " + msg.type + ' ' + msg.target.src) else alert("window error: " + msg) return false } + +// function called before the page is closed +window.onbeforeunload = function () { + if (chg) // if the textarea has been modified + return "" // ask close page confirmation +} var abc_images, // image buffer abc_fname = ["noname.abc", ""], // file names abc_mtime = [], // associated last modification time abc, // Abc object @@ -39,10 +45,11 @@ selx = [0, 0], // selected source indexes selx_sav = [], // (saved while playing/printing) play = {}, // play data pop, // current popup message texts = {}, // language specific texts + chg = 0, // > 0 when the textarea is modified jsdir = document.currentScript ? document.currentScript.src.match(/.*\//) : (function() { var s_a = document.getElementsByTagName('script') for (var k = 0; k < s_a.length; k++) { @@ -166,10 +173,11 @@ reader.onloadend = function(evt) { var s = srcidx == 0 ? "source" : "src1"; elt_ref[s].value = evt.target.result; elt_ref["s" + srcidx].value = abc_fname[srcidx]; + chg = -1 // no change yet src_change() } // Read the file as text reader.readAsText(files[0], "UTF-8") @@ -209,10 +217,11 @@ return } } elt_ref.diverr.innerHTML = ''; selx[0] = selx[1] = 0; + chg++ // textarea modified render2() } function render2() { var content = elt_ref.source.value, def = (abc2svg.a_inc && abc2svg.a_inc["default.abc"]) || '' @@ -449,10 +458,12 @@ // add the link to the DOM document.body.appendChild(link); // click the new link link.click() + + chg = 0 // no change anymore } // destroy the clicked element function destroyClickedElement(evt) { document.body.removeChild(evt.target) @@ -537,11 +548,10 @@ } return } // search a symbol to play - function gnrn(sym, loop) { // go to the next real note (not tied) var i while (1) { switch (sym.type) { case C.NOTE: @@ -571,10 +581,11 @@ } sym = sym.ts_next } // not reached } + function gprn(sym, loop) { // go to the previous real note (not tied) var i while (1) { switch (sym.type) { // case C.BAR: @@ -609,11 +620,15 @@ } // not reached } function gsot(si) { // go to the first symbol of a tune - return syms[si].p_v.sym + var sym = syms[si].p_v.sym + + while (!sym.seqst) + sym = sym.ts_prev + return sym } function get_se(si) { // get the starting symbol var sym = syms[si] while (!sym.seqst) @@ -893,10 +908,11 @@ s = srcidx == 0 ? "source" : "src1" elt_ref["s" + srcidx].value = abc_fname[srcidx] = data[0].name reader.onload = function(evt) { elt_ref[s].value = evt.target.result + chg = -1 // no change yet src_change() } reader.readAsText(data[0],"UTF-8") // return } Index: editor/edit.xhtml ================================================================== --- editor/edit.xhtml +++ editor/edit.xhtml @@ -14,56 +14,56 @@