ADDED MIT.txt Index: MIT.txt ================================================================== --- /dev/null +++ MIT.txt @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. Index: README.md ================================================================== --- README.md +++ README.md @@ -3,33 +3,28 @@ li{margin-left:.5cm;max-width:20cm} li p{margin-left:0} ## abc2svg -**abc2svg** is a set of Javascript files for handling the - [ABC music notation](http://abcnotation.com/). This includes +**abc2svg** is a set of ECMAscript files that handle the +[ABC music notation](http://abcnotation.com/). This includes editing, displaying, printing, playing the music files and converting them to other formats such as ABC and MEI notations. The **abc2svg** core is based on the -[abcm2ps](https://github.com/leesavide/abcm2ps) C code +[abcm2ps](https://github.com/lewdlime/abcm2ps) C code which requires compilation on every operating system. The **abc2svg** scripts can run in any system with no compilation on -any platform that contains an internet browser. This includes Windows, +any platform that contains an internet browser. This includes MS-Windows, Apple, and Unix-like systems (Linux, BSD...) as well as portable devices such as cell phones and iPads. A description of the ABC parameters that relate to both abcm2ps and abc2svg can be found [here][1]. [1]: http://moinejf.free.fr/abcm2ps-doc/index.html "abc2svg documentation" -Note: The ABC music notation is now discussed in the mailing list -[abcusers at groups.io](https://groups.io/g/abcusers). -Some work in progress about the ABC draft may be found in these -[temporary pages](http://moinejf.free.fr/abcstd/). - ### 1. Web browser usage #### 1.1 Rendering ABC files from a local or remote source After accessing ABC files with a web browser either from a local or web @@ -56,11 +51,11 @@ complexity of the abc code, it should be replaced by a music representation or a list of the contents of the ABC file. Here are two bookmarklets that you can try. This -first abc2svg bookmarklet renders all the music it finds in the page currently displayed. Once the music is displayed, clicking inside a tune starts playing it from the beginning. Clicking on a particular note or rest starts playing from that point. @@ -69,14 +64,14 @@ (If the 'Print' button does not appear on your browser menu, try right clicking on the web page.) Alternatively, if your source contains many tunes, you can use this -second bookmarklet. -The browser will list the titles of the tunes. Click on one of the titles -to view the music representation. +The browser will list the titles of the tunes. +Clicking on a title displays the tune. Playing and printing work in the same manner as above. The generated pages contain a yellow menu in the top right corner which permits you to return to the tune list or to modify the music. With this last option, you can adjust the page size before printing, correct the ABC syntax or do some transposition. @@ -134,11 +129,11 @@ ABC sequences in the <body>. [This example][4] demonstrates how to do it. (Note that, in the example, the paths are relative - see why below.) -[4]: http://moinejf.free.fr/abcm2ps-doc/tabac.xhtml "J'ai du bon tabac" +[4]: http://moinejf.free.fr/abcm2ps-doc/tabac.html "J'ai du bon tabac" As it is apparent, HTML and ABC can be mixed in the same html file. Both are rendered in the order you defined them. You may also have noticed this CSS about SVG elements: @@ -151,11 +146,11 @@ Global ABC parameters may be added as `parameter=value` in the query string of the URL of the page. The [following example][16] calls the same "J'ai du bon tabac" with `pagescale=1.2` (giving `%%pagescale 1.2`). -[16]: http://moinejf.free.fr/abcm2ps-doc/tabac.xhtml?pagescale=1.2 "J'ai du bon tabac" +[16]: http://moinejf.free.fr/abcm2ps-doc/tabac.html?pagescale=1.2 "J'ai du bon tabac" In the above examples, all the ABC music is generated (displayed and ready to be played) by means of the script **abcweb-1.js**. If there is a large collection of tunes, it may be preferable to link to the other script, **abcweb1-1.js**, which permits you @@ -166,11 +161,11 @@ A menu on the top right corner of the screen permits to get the tune list again. Here is [a real example][6]. [6]: http://moinejf.free.fr/abc/boyvin-2-2.html "J. Boyvin organ tunes" -As you may note in the menu, the edition of the ABC content is proposed. +As you may notice in the menu, the edition of the ABC content is proposed. This permits, for example, to transpose the tunes. This edition is done inside the browser, so, your changes will be lost after leaving the page. When accessing such pages, an external selection of the tunes @@ -180,15 +175,10 @@ For instance, here is the ['Duo' de J. Boyvin][8]. [7]: https://en.wikibooks.org/wiki/Regular_Expressions "Regular expressions" [8]: http://moinejf.free.fr/abc/boyvin-2-2.html#T:Duo "Duo" -In the first example ("J'ai du bon tabac"), you saw that care -should be taken with regard to the special XML characters -'<', '>' and '&' by enclosing -the ABC sequences inside XML comments. - With any of the above scripts the music may be printed using the 'Print' button of the browser. You should add a style as: `       @media print{body{margin:0;padding:0;border:0}.nop{display:none}}` to remove the margins, the error messages and the menus from the @@ -205,22 +195,22 @@ - [Guido Gonzato's page](http://abcplus.sourceforge.net/#abc2svg) Guido maintains a ZIP archive of the abc2vg scripts after each release of a new version (many thanks, Guido!). You can just download and unzip this archive. **abc2svg** should run immediately in your machine without connecting to my site. - In the same page, you can also find some binaries for the fastest - javascript interpreter (QuickJS - see below). + In the same page, you can also find some binaries of QuickJS, + a fast javascript interpreter (see below). - tarball From the timeline in the [chisel repository][13], you can get a tarball any version of the abc2svg source and install it in your system. The abc2svg scripts must then be built from the raw source files described in the section 'Build' below. The disadvantage of this approach is that if you want to use an other or newer version you need to download a new tarball. -[13]: https://chiselapp.com/user/moinejf/repository/abc2svg/timeline) +[13]: https://chiselapp.com/user/moinejf/repository/abc2svg/timeline - fossil clone If you can get the fossil program (one binary) for your system, you may clone the chisel repository by `        fossil clone https://chiselapp.com/user/moinejf/repository/abc2svg abc2svg.fossil` @@ -228,11 +218,11 @@ and get the abc2svg source files containing the last changes between the official versions. Updating your files is done by `        fossil pull` `        fossil update` Building the scripts is done in the same way as with a tarball. - The repository is presently over 23Mb. + The repository is presently over 34Mb. For those unfamiliar with [fossil][14], it is an integrated software management system similar to [git](https://git-scm.com/). Chisel acts like a repository similar to [github](https://github.com/). @@ -274,15 +264,12 @@ The interfaces to the various scripts are different. Below you will find various scripts that I had to built. - `abcqjs` with `qjs` [QuickJS by Fabrice Bellard and Charlie Gordon][10] -- `abcjs24` with `js24` (Mozilla JavaScript shell - Spidermonkey) -- `abcjs52` with `js52` (Mozilla JavaScript shell - Spidermonkey) -- `abcjs60` with `js60` (Mozilla JavaScript shell - Spidermonkey) -- `abcjsc` with `jsc-1` (webkitgtk2) -- `abcv8` with `d8` (Google libv8) +- `abcmjs` with `js78`, `js68`, `js60`, `js52` or `js24` (Mozilla JavaScript shell) +- `abcjsc` with `jsc` (webkit2gtk) - `abcnode` with `node` (nodeJS without module) - `abc2svg` with `node` (nodeJS with modules) [10]: https://bellard.org/quickjs/ @@ -294,10 +281,15 @@ - `script.js` is an optional backend script. It defaults to `tohtml.js` (HTML+SVG) - `options` are the ABC options. For compatibility, the last options are moved before the last ABC file. + +These scripts try to read a file `default.abc` at startup time. +This file and also the files included by `%%abc-include` are searched +in the current directory or in the colon separated list of directories +contained in the environment variable `ABCPATH`. #### 2.2 Backend scripts By default, the shell scripts generate (HTML+SVG) files. This output may be modified by backend scripts. These ones must appear @@ -316,17 +308,20 @@ word processors (abiword, libreoffice...). The word processor allows you to convert the file to many other formats from a command line. The abc2svg music font (`abc2svf.woff` or `abc2svg.ttf`) must be installed in the local system for displaying and/or converting the .abw file. Example: - `        abcv8 toabw.js my_file.abc > my_file.abw` + `        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). +- `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. @@ -436,12 +431,11 @@ - Playing uses the HTML5 audio and/or the midi APIs. For audio, by default, abc2svg uses a sound font (format SF2) which is split into one file per instrument. This sound font is stored in the subdirectory `Scc1t2/`. - Each instrument file is a base64 encoded javascript array. - + Each instrument file is a base64 encoded javascript array. Other sound fonts may be used. Some of them are stored in the subdirectory `sf2/` (`AWE_ROM_gm` and `2MBGMGS`). Two formats are supported: raw SF2 and SF2 wrapped into javascript (the raw SF2 files can be loaded only when they are in the same HTTP domain). The shell script `sf.sh` (in `sf2/`) may be used to create the javacript files @@ -457,18 +451,19 @@ Here are the scripts which are used in a web context: - `abcweb-1.js` This script replaces the ABC or MEI sequences found in the (X)HTML file - by SVG images of the music. The ABC sequences may start on `X:` or `%abc-` - at start of line, and stop on any XML tag also at start of line - (see below for MEI). - They may also appear after a (X)HTML element of the class "abc" - (lower case letters) or inside a <script> with a type - ["text/vnd.abc"](https://www.iana.org/assignments/media-types/text/vnd.abc) - (the script element is removed from the page). - When a ABC sequence is not included in a <script> and when it contains + 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 removed from the page), + - or inside (X)HTML elements with a class `abc` (lower case letters), + - otherwise on `X:` or `%abc-` at start of line up to a XML tag at start of line. + + When a ABC sequence is not included in a <script> and when it contains the characters '<', '>' or '&', it must be enclosed in a XML comment or in a CDATA (%<![CDATA[ .. %]]> - the comment or CDATA must be in a ABC comment). See the [%%beginml documentation](http://moinejf.free.fr/abcm2ps-doc/beginml.xhtml) @@ -477,37 +472,41 @@ the script `snd-1.js` (see below). [15]: http://moinejf.free.fr/abcm2ps-doc/inline.html "abc2svg - inline music" - `abcweb1-1.js` - This script replaces all the page body by music as SVG images. - As with abcweb-1.js, the music sequences start on `X:` or `%abc` - at start of line, stop on any ML tag, a XML comment must be used - when there are special XML characters, and playing is possible. - When there are many tunes in the file, the script displays a list + The page body is analyzed as a ABC file and its content is + replaced by music as SVG images. + If the page contains reserved XML characters ('<', '>' and '&'), + the ABC code must be enclosed in a + ` ` + sequence. + + When there are many tunes in the file, the script displays a list of the tunes. The list step may be bypassed when the URL of the file contains a regular expression in the 'hash' value ('#' followed by a string at the end of the URL). This string does a - [%%select](http://moinejf.free.fr/abcm2ps-doc/select.xhtml). - When one or many tunes are displayed, a menu in the top/right corner + [%%select](http://moinejf.free.fr/abcm2ps-doc/select.xhtml). + + When one or many tunes are displayed, a menu in the top/right corner offers to go back to the tune list or to modify the ABC source. - `snd-1.js` - This script may be used with `abcweb{,1}-1.js` to play the rendered - ABC music. + This script may be used with `abcweb-1.js` or `abcweb1-1.js` + to play the rendered ABC music. - `abcdoc-1.js` This script is mainly used for ABC documentation. It lets the ABC source sequences in the page before the SVG images. See the source of - [abcm2ps/abc2svg features](http://moinejf.free.fr/abcm2ps-doc/features.xhtml) + [abcm2ps/abc2svg features](http://moinejf.free.fr/abcm2ps-doc/features.html) for an example. ### 5. MEI support -As an experimental feature, an extented core `mei2svg-1.js` may be generated. +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 Index: abc2svg ================================================================== --- abc2svg +++ abc2svg @@ -21,11 +21,15 @@ process.exit(1) } abc2svg.readFile = function(fname) { var fs = require("fs") - return fs.readFileSync(fname).toString() + try { + return fs.readFileSync(fname).toString() + } catch(e) { + } + return null } abc2svg.get_mtime = function(fn) { var fs = require("fs") @@ -49,11 +53,16 @@ // load the command line m = require("abc2svg/cmdline"); global.user = m.user; global.abc_cmd = m.abc_cmd +// set the ABC path +var path = process.env.ABCPATH +if (path) + abc2svg.path = path.split(':') + // main var args = process.argv args.shift(); args.shift() -abc_cmd("nodeJS", args, "Node.js") +abc_cmd("abc2svg", args, "nodeJS") Index: abc2svg.ttf ================================================================== --- abc2svg.ttf +++ abc2svg.ttf cannot compute difference between binary files Index: abcjsc ================================================================== --- abcjsc +++ abcjsc @@ -1,10 +1,18 @@ #!/bin/sh # abc2svg with jsc (webkit2gtk) -progdir=`dirname $0` +# search where are the abc2svg scripts +c=$0 +if [ `dirname $c` = . ]; then + c=`which $c` +fi +if [ -L $c ]; then + c=`realpath $c` +fi +c=`dirname $c` -exec jsc -e "var progdir='${progdir}/' +exec jsc -e "var progdir='${c}/' // function to do utf-8 work... // uncommenting this function asks for also uncommenting its call in cmdline.js //function utf_convert(fin) { // var b1, b2, b3, @@ -48,11 +56,15 @@ }, quit: function() { quit(1) }, readFile: function(fname) { - return read(fname) + try { + return read(fname) + } catch(e) { + } + return null }, loadjs: function(fn, relay, onerror) { try { load(progdir + fn) if (relay) @@ -68,13 +80,21 @@ load(progdir + 'abc2svg-1.js') load(progdir + 'cmdline.js') function main(args) { + var i = '${ABCPATH}' + + console = { + log: printErr + } + + if (i) + abc2svg.path = i.split(':') abc_cmd('abcjsc', args, 'jsc (webkit2gtk)') } if (typeof arguments == 'undefined') var arguments = '' main(arguments) " -- "$@" ADDED abcmjs Index: abcmjs ================================================================== --- /dev/null +++ abcmjs @@ -0,0 +1,66 @@ +:;# abc2svg with a Mozilla JavaScript interpreter + +for c in js78 js68 js60 js52 js24 end; do + if [ $c = 'end' ]; then + echo "$0: could not find a javascript interpreter - abort" + exit 1 + fi + if command -v $c >/dev/null 2>&1; then + break + fi +done + +# search where are the abc2svg scripts +p=$0 +if [ `dirname $p` = . ]; then + p=`which $p` +fi +if [ -L $p ]; then + p=`realpath $p` +fi +p=`dirname $p` + +exec $c -e "// interpreter specific functions +var abc2svg = { + print: print, + printErr: printErr, + quit: function() { + quit(1) + }, + readFile: function(fn) { + try { + return read(fn) + } catch(e) { + } + return null + }, + loadjs: function(fn, relay, onerror) { + try { + load(fn[0] == '/' ? fn : (path + fn)) + if (relay) + relay() + } catch(e) { + if (onerror) + onerror() + else + printErr('Cannot read file ' + fn) + } + } // loadjs() +} // abc2svg + + var args = scriptArgs, + path = '${ABCPATH}' + + if (path) + abc2svg.path = path.split(':') + + path = '${p}/' // path to the abc2svg scripts + + console.log = printErr + +load(path + 'abc2svg-1.js') +load(path + 'cmdline.js') + +// main +abc_cmd('abcmjs', args, '${c}') +" -- "$@" Index: abcnode ================================================================== --- abcnode +++ abcnode @@ -1,9 +1,18 @@ :;# abc2svg with raw nodeJS -progdir=`dirname $0` + +# search where are the abc2svg scripts +p=$0 +if [ `dirname $p` = . ]; then + p=`which $p` +fi +if [ -L $p ]; then + p=`realpath $p` +fi +p=`dirname $p` -cat ${progdir}/abc2svg-1.js ${progdir}/cmdline.js > /tmp/toto.js +cat ${p}/abc2svg-1.js ${p}/cmdline.js > /tmp/toto.js case $1 in *.js) cat $1 >> /tmp/toto.js if grep 'abc2svg.abc_init =' "$1" > /dev/null; then x="no" @@ -10,15 +19,15 @@ fi shift ;; esac if [ "x$x" != 'xno' ]; then - cat ${progdir}/tohtml.js >> /tmp/toto.js + cat ${p}/tohtml.js >> /tmp/toto.js fi cat <> /tmp/toto.js - var progdir='${progdir}/' + var path='${p}/' // interpreter specific functions abc2svg.print = function(str) { process.stdout.write(str + '\n') } @@ -29,11 +38,15 @@ process.exit(1) } abc2svg.readFile = function(fname) { var fs = require("fs"); - return fs.readFileSync(fname).toString() + try { + return fs.readFileSync(fname).toString() + } catch(e) { + } + return null } abc2svg.get_mtime= function(fn) { var fs = require("fs") @@ -41,11 +54,11 @@ } abc2svg.loadjs = function(fn, relay, onerror) { try { var geval = eval; - geval(abc2svg.readFile(progdir + fn)) + geval(abc2svg.readFile(path + fn)) if (relay) relay() } catch(e) { if (onerror) onerror() @@ -57,13 +70,17 @@ // (this file is wrapped) global.Abc = Abc global.abc2svg = abc2svg // main - var args = process.argv + var args = process.argv, + i = '${ABCPATH}' + if (i) + abc2svg.path = i.split(':') + args.shift() args.shift() global.user = user; // (for modules/page.js) - abc_cmd("abcnode", process.argv, "nodeJS") + abc_cmd("abcnode", args, "nodeJS") EOF node /tmp/toto.js "$@" Index: abcqjs ================================================================== --- abcqjs +++ abcqjs @@ -1,63 +1,64 @@ :;# shell script to convert ABC or MEI music notation to other format # using abc2svg and quickjs (https://bellard.org/quickjs/) + +# search where are the abc2svg scripts +p=$0 +if [ `dirname $p` = . ]; then + p=`which $p` +fi +if [ -L $p ]; then + p=`realpath $p` +fi +p=`dirname $p` exec qjs --std -e ' var i, args = scriptArgs, core = "abc2svg-1.js", - path = args.shift(). // the 1st argument is the script name - match(/.*\//) + path = args.shift() + "/" // the 1st argument is the script path // interpreter specific functions function load(fn) { return std.loadScript(fn) } var abc2svg = { - print: function(str) { - print(str) - }, + print: print, printErr: function(str) { std.err.printf("%s\n", str) }, quit: function() { std.exit(1) }, - readFile: function(fname) { - var f, b - - f = std.open(fname, "r") - if (!f) - return "" - - b = f.readAsString() - f.close() - return b - }, // readfile() + readFile: std.loadFile, get_mtime: function(fn) { return new Date(os.stat(fn)[0].mtime) }, loadjs: function(fn, relay, onerror) { try { load(fn[0] == "/" ? fn : (path + fn)) + if (relay) + relay() } catch(e) { if (onerror) onerror() else abc2svg.printErr("Cannot read file " + fn + "\n " + e.name + ": " + e.message) return } - if (relay) - relay() } // loadjs() } // abc2svg // --- main --- - path = path ? path[0] : "" + console.log = abc2svg.printErr + + i = std.getenv("ABCPATH") + if (i) + abc2svg.path = i.split(":") i = args.length while (--i >= 0) { if (/\.mei$/.test(args[i])) { core = "mei2svg-1.js" @@ -67,10 +68,10 @@ load(path + core) load(path + "cmdline.js") abc_cmd("abcqjs", args, "QuickJS") -' $0 "$@" +' $p "$@" The previous solution with the shebang line as #!/usr/bin/env -S qjs --std did not work with non-GNU versions of env Index: abctopdf ================================================================== --- abctopdf +++ abctopdf @@ -1,22 +1,20 @@ :;# abctopdf # convert ABC to PDF using abc2svg and one of # - a chrome/chromium compatible web browser -# (if not found, set its name in the environment variable 'BROWSER') +# (if not found, you can set its name in the environment variable 'BROWSER') # - weasyprint (https://weasyprint.org/) -# - rsvg-convert (https://wiki.gnome.org/Projects/LibRsvg) # -# Note: when weasyprint or rsvg-convert are used, +# Note: when weasyprint is used, # this script forces A4 as the paper size. # For an other size, change -# 'size: A4' and '--pageheight 29.7cm' with weasyprint -# '%%pageheight 29.7cm' with rsvg-convert +# 'size: A4' and '--pageheight 29.7cm' # -# Copyright (C) 2019-2020 Jean-François Moine - License GPL3+ +# Copyright (C) 2019-2021 Jean-François Moine - License GPL3+ # set a browser -if [ x$BROWSER = x ]; then +if [ "x$BROWSER" = "x" ]; then BROWSER=chromium fi if [ $# -eq 0 ]; then cat << EOF @@ -86,40 +84,31 @@ fi shift done # check if some chromium-based browser is available -for c in $BROWSER chromium-browser vivaldi opera brave-browser epic google-chrome; do - if command -v $c >/dev/null 2>&1; then +for c in "$BROWSER" chromium-browser vivaldi opera brave-browser epic google-chrome; do + if command -v "$c" >/dev/null 2>&1; then echo "and $c" command $abcscr "$@" > /tmp/o.html - $c --headless --disable-gpu --print-to-pdf=$out /tmp/o.html + "$c" --headless --disable-gpu\ + --print-to-pdf=$out --print-to-pdf-no-header /tmp/o.html rm /tmp/o.html exit 0 fi done # try weasyprint if command -v weasyprint >/dev/null 2>&1; then echo "and weasyprint" - rm -f /tmp/abc*.svg + rm -f /tmp/abc*.svg /tmp/abc.html # generate a (HTML+SVG) file # then, extract the SVG images (pages) to /tmp/ and build a file.html - cat > /tmp/abc.html < - - - -EOF -n=0 + touch /tmp/abc.html + n=0 command $abcscr --pageheight 29.7cm --fullsvg 1 --musicfont abc2svg "$@" | while read v; do case "$v" in "> $fn fn= ;; + "") + echo '' >> /tmp/abc.html + ;; "> $fn @@ -147,165 +139,7 @@ weasyprint -f pdf /tmp/abc.html $out rm -f /tmp/abc*.svg /tmp/abc.html exit 0 fi -# last chance with rsvg-convert -# check the required programs -if ! command -v rsvg-convert >/dev/null 2>&1; then - echo "$0: program 'rsvg-convert' not found - abort" + echo "$0: no program found for SVG to PDF translation - abort" exit 1 -fi -echo "and rsvg-convert" - -# build a ABC file (with JS script) to change the font and page definitions -# this solves the following problems: -# - the maintainer of librsvg does not want to handle the CSS shorthand 'font:' -# - rsvg-convert always gets the SVG images at 72ppi, ignoring the -p/-d values -# - the sets of SVG images per page must be converted to a set of one SVG per page -cat > /tmp/fix.abc << EOF -%%pageheight 29.7cm -%%fullsvg 1 -%%musicfont abc2svg -%%beginjs - var fix_out -function font_bug(str) { - return str.replace(/font:.*?["};]/g, function(p) { // " - var w = p.slice(5, -1).match(/[^ \t"]+|".+?"/g), // " - l = w.length, - r = 'font-family:' + w[--l] + ';font-size:' + w[--l] - while (--l >= 0) { - switch (w[l]) { - case 'italic': - r += ';font-style:italic' - break - case 'oblique': - r += ';font-style:oblique' - break - case 'bold': - r += ';font-weight:bold' - break - } - } - return r + p[p.length - 1] - }) -} -// install -// - front-end - fix_out = abc2svg.page.img_out - abc2svg.page.img_out = function(page, p) { - fix_out(page, font_bug(p)) - } -// - back-end - var page_h, page_out, - page_cl = "", - page_sty = "" - - // get the style from the image and remove it from the block - function get_style(p) { - var i, - sty = p.match(/((.|\n)*?)<\/style>/) - - if (!sty) - return p - sty = sty[1].split('\n') - if (!page_sty) - page_sty = '\n' - for (i = 0; i < sty.length; i++) { - if (page_sty.indexOf(sty[i]) < 0) - page_sty = page_sty.replace('\n', - sty[i] + '\n\n') - } - return p.replace(/\n/, '') - } // get_style() - - // low level output - abc.page.user_out = function(p) { - var h, w, - cfmt = abc.cfmt() - - switch (p.slice(0, 4)) { - case "]*>/) // " - if (h) - page_cl = h[2] - } - page_out += p - .replace(//, - '') - .replace('', '\n') - page_h += Number(p.match(/height="(\d+)px"/)[1]) - break - case "\n' + - page_sty + - page_out + "") - break - } - } // user_out() -// for tests -abc2svg.abort = function(e) { -abc2svg.printErr('abort: '+e.message+'\n'+e.stack) -abc2svg.quit() -} -%%endjs -EOF - -# purge /tmp -rm -f /tmp/abc*.svg - -# generate a (HTML+SVG) file with a abc2svg batch script -# then, extract the SVG images (pages) to /tmp -n=0 -command $abcscr /tmp/fix.abc "$@" | while read v; do - case "$v" in - " $fn - ;; - "> $fn - fn= - ;; - "> $fn - fi - ;; - esac -done - -# convert the SVG images to PDF -if [ -f /tmp/abc1.svg ]; then - rsvg-convert -f pdf $(ls -rt /tmp/abc*.svg) -o $out -else - echo 'Errors during the generation' -fi - -# cleanup -rm -f /tmp/abc*.svg /tmp/fix.abc Index: build ================================================================== --- build +++ build @@ -55,15 +55,17 @@ gen modules/grid2.js > grid2-$MAJOR.js gen modules/grid3.js > grid3-$MAJOR.js gen modules/jazzchord.js > jazzchord-$MAJOR.js gen modules/jianpu.js > jianpu-$MAJOR.js gen modules/mdnn.js > mdnn-$MAJOR.js +gen modules/nns.js > nns-$MAJOR.js gen modules/MIDI.js > MIDI-$MAJOR.js gen modules/page.js modules/strftime.js > page-$MAJOR.js gen modules/pedline.js > pedline-$MAJOR.js gen modules/perc.js > perc-$MAJOR.js gen modules/wps.js modules/psvg.js > psvg-$MAJOR.js +gen modules/roman.js > roman-$MAJOR.js gen modules/soloffs.js > soloffs-$MAJOR.js gen modules/sth.js > sth-$MAJOR.js gen modules/strtab.js > strtab-$MAJOR.js gen modules/temper.js > temper-$MAJOR.js Index: build.ninja ================================================================== --- build.ninja +++ build.ninja @@ -110,14 +110,16 @@ build grid3-$MAJOR.js: jscu modules/grid3.js build jazzchord-$MAJOR.js: jscu modules/jazzchord.js build jianpu-$MAJOR.js: jscu modules/jianpu.js build mdnn-$MAJOR.js: jscu modules/mdnn.js build MIDI-$MAJOR.js: jscu modules/MIDI.js +build nns-$MAJOR.js: jscu modules/nns.js build page-$MAJOR.js: jscu modules/page.js modules/strftime.js build pedline-$MAJOR.js: jscu modules/pedline.js build perc-$MAJOR.js: jscu modules/perc.js build psvg-$MAJOR.js: jscu modules/wps.js modules/psvg.js +build roman-$MAJOR.js: jscu modules/roman.js build soloffs-$MAJOR.js: jscu modules/soloffs.js build sth-$MAJOR.js: jscu modules/sth.js build strtab-$MAJOR.js: jscu modules/strtab.js build temper-$MAJOR.js: jscu modules/temper.js @@ -149,13 +151,13 @@ json-$MAJOR.js toaudio-$MAJOR.js snd-$MAJOR.js play-$MAJOR.js psvg-$MAJOR.js $ ambitus-$MAJOR.js break-$MAJOR.js capo-$MAJOR.js $ chordnames-$MAJOR.js clair-$MAJOR.js clip-$MAJOR.js $ combine-$MAJOR.js diag-$MAJOR.js equalbars-$MAJOR.js gamelan-$MAJOR.js $ grid-$MAJOR.js grid2-$MAJOR.js grid3-$MAJOR.js jianpu-$MAJOR.js $ - jazzchord-$MAJOR.js mdnn-$MAJOR.js $ - MIDI-$MAJOR.js page-$MAJOR.js pedline-$MAJOR.js perc-$MAJOR.js $ - soloffs-$MAJOR.js sth-$MAJOR.js strtab-$MAJOR.js $ + jazzchord-$MAJOR.js mdnn-$MAJOR.js MIDI-$MAJOR.js $ + nns-$MAJOR.js page-$MAJOR.js pedline-$MAJOR.js perc-$MAJOR.js $ + roman-$MAJOR.js soloffs-$MAJOR.js sth-$MAJOR.js strtab-$MAJOR.js $ temper-$MAJOR.js edit-$MAJOR.css edit-$MAJOR.js edit-$MAJOR.xhtml $ abckbd-$MAJOR.js abckbd2-$MAJOR.js follow-$MAJOR.js # not mei2svg-$MAJOR.js default all @@ -164,13 +166,13 @@ rule version command = npm version $out;$ tag=`ag -Q '"version"' package.json|cut -d'"' -f4`;$ fossil commit -m "New release v$$tag" -tag v$$tag;$ $tool -v;$ - npm publish + echo 'do "npm publish --otp "' build minor: version build patch: version # temporary rule nul command = touch $out build follow-$MAJOR.js: nul Index: cmdline.js ================================================================== --- cmdline.js +++ cmdline.js @@ -1,8 +1,8 @@ // abc2svg - cmdline.js - command line // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2021 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 @@ -19,14 +19,34 @@ // user definitions var user = { read_file: function(fn) { // read a file (main or included) var i, - file = abc2svg.readFile(fn) + p = fn, + file = abc2svg.readFile(p) + + if (!file && fn[0] != '/') { + for (i = 0; i < abc2svg.path.length; i++) { + p = abc2svg.path[i] + '/' + fn + file = abc2svg.readFile(p) + if (file) + break + } + } if (!file) return file + + // memorize the file path + i = p.lastIndexOf('/') + if (i > 0) { + p = p.slice(0, i) + if (abc2svg.path.indexOf(p) < 0) + abc2svg.path.unshift(p) + } + + // convert the file content into a Unix string i = file.indexOf('\r') if (i >= 0) { if (file[i + 1] == '\n') file = file.replace(/\r\n/g, '\n') // M$ else @@ -36,39 +56,26 @@ // load the required modules (synchronous) abc2svg.modules.load(file) return file }, - errtxt: '' -} - - // print or store the error messages - if (typeof abc2svg.printErr == 'function') - user.errmsg = function(msg, l, c) { abc2svg.printErr(msg) } - else - user.errmsg = function(msg, l, c) { user.errtxt += msg + '\n' } + errtxt: '', + errmsg: // print or store the error messages + typeof abc2svg.printErr == 'function' + ? function(msg, l, c) { abc2svg.printErr(msg) } + : function(msg, l, c) { user.errtxt += msg + '\n' } +} // user var abc // (global for 'toxxx.js') +if (!abc2svg.path) + abc2svg.path = [] // path to ABC files - from env ABCPATH + // treat a file function do_file(fn) { - var ext, file - - try { - file = user.read_file(fn) - } catch (e) { - - // get the file extension - ext = fn.slice((fn.lastIndexOf('.') - 1 >>> 0) + 2) - if (!ext) { - fn += ".abc" - try { - file = user.read_file(fn) - } catch (e) { - } - } - } + var file = user.read_file(fn) + if (!file) { if (fn != "default.abc") user.errmsg("Cannot read file '" + fn + "'") return } @@ -110,11 +117,11 @@ a.splice(i, 1) a.push(f) } // arg_reorder() // if the first argument is a javascript file, load it - if (/\.js$/.test(args[0])) { + if (args[0] && args[0].slice(-3) == '.js') { abc2svg.loadjs(args[0]) args.shift() } if (!args[0]) { Index: core/abc2svg.js ================================================================== --- core/abc2svg.js +++ core/abc2svg.js @@ -1,8 +1,8 @@ // abc2svg - abc2svg.js // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2021 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 @@ -48,16 +48,20 @@ EMPTY: 1, OVAL: 2, OVALBARS: 3, SQUARE: 4, - // slur/tie types (3 + 1 bits) - SL_ABOVE: 0x01, + // position types + SL_ABOVE: 0x01, // position (3 bits) SL_BELOW: 0x02, SL_AUTO: 0x03, SL_HIDDEN: 0x04, - SL_DOTTED: 0x08 // (modifier bit) + SL_DOTTED: 0x08, // modifiers + SL_ALI_MSK: 0x70, // align + SL_ALIGN: 0x10, + SL_CENTER: 0x20, + SL_CLOSE: 0x40 }; // !! tied to the symbol types in abc2svg.js !! abc2svg.sym_name = ['bar', 'clef', 'custos', '', 'grace', 'key', 'meter', 'Zrest', 'note', 'part', @@ -171,10 +175,52 @@ "7+5": "+7", "7#5": "+7", "sus": "sus4", "7sus": "7sus4" } // ch_alias + +// extract one of the chord symbols +// With chords as "xxx(yyy)" or "[yyy];xxx" +// (!sel - default) returns "xxx" and (sel) returns "yyy" +abc2svg.cs_sel0 = /[\[(].*[\])]/g +abc2svg.cs_sel1 = /.*[\[(]|[\])].*/g +abc2svg.cs_filter = function(a_cs, sel) { + var i, cs, + tcs = "" + + for (i = 0; i < a_cs.length; i++) { + cs = a_cs[i] + if (cs.type == 'g') + tcs += cs.text + } + return tcs.replace(sel ? abc2svg.cs_sel1 : abc2svg.cs_sel0, '') +} // cs_filter() + +// font weight +// reference: +// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight +abc2svg.ft_w = { + thin: 100, + extralight: 200, + light: 300, + regular: 400, + medium: 500, + semi: 600, + demi: 600, + semibold: 600, + demibold: 600, + bold: 700, + extrabold: 800, + ultrabold: 800, + black: 900, + heavy: 900 +} +abc2svg.ft_re = new RegExp('\ +-?Thin|-?Extra Light|-?Light|-?Regular|-?Medium|\ +-?[DS]emi|-?[DS]emi[ -]?Bold|\ +-?Bold|-?Extra[ -]?Bold|-?Ultra[ -]?Bold|-?Black|-?Heavy/', + "i") // simplify a rational number n/d abc2svg.rat = function(n, d) { var a, t, n0 = 0, @@ -232,11 +278,11 @@ CLOSE_BRACKET2 = 0x0800, MASTER_VOICE = 0x1000, IN = 96, // resolution 96 PPI CM = 37.8, // 1 inch = 2.54 centimeter - YSTEP = 256 /* number of steps for y offsets */ + YSTEP // number of steps for y offsets // error texts var errs = { bad_char: "Bad character '$1'", bad_grace: "Bad character in grace note sequence", @@ -280,11 +326,11 @@ if (!obj) return obj var tmp = new obj.constructor for (var k in obj) if (obj.hasOwnProperty(k)) { - if (lvl && typeof obj[k] != "number") + if (lvl && typeof obj[k] == "object") tmp[k] = clone(obj[k], lvl - 1) else tmp[k] = obj[k] } return tmp Index: core/deco.js ================================================================== --- core/deco.js +++ core/deco.js @@ -1,8 +1,8 @@ // abc2svg - deco.js - decorations // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2022 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -50,78 +50,90 @@ // x, // x offset // y, // y offset // } var dd_tb = {}, // definition of the decorations - a_de // array of the decoration elements + a_de, // array of the decoration elements + cross // cross voice decorations // decorations - populate with standard decorations var decos = { - dot: "0 stc 5 1 1", - tenuto: "0 emb 5 3 3", + dot: "0 stc 6 1 1", + tenuto: "0 emb 6 3 3", slide: "1 sld 3 7 0", - arpeggio: "2 arp 12 10 0", - roll: "3 roll 7 6 6", - fermata: "3 hld 10 7 7", - emphasis: "3 accent 7 4 4", - lowermordent: "3 lmrd 10 5 5", - coda: "3 coda 24 10 10", - uppermordent: "3 umrd 10 5 5", - segno: "3 sgno 22 8 8", + 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 7 3 3", // (staccatissimo or spiccato) - turnx: "3 turnx 10 0 5", - breath: "3 brth 0 1 20", + 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", - invertedfermata: "3 hld 10 7 7", - invertedturn: "3 turn 10 0 5", - invertedturnx: "3 turnx 10 0 5", - "0": "3 fng 8 3 3 0", - "1": "3 fng 8 3 3 1", - "2": "3 fng 8 3 3 2", - "3": "3 fng 8 3 3 3", - "4": "3 fng 8 3 3 4", - "5": "3 fng 8 3 3 5", + turnx: "3 turnx 5,5 6 6", + invertedturn: "3 turn 5,5 6 6", + invertedturnx: "3 turnx 5,5 6 6", + "0": "3 fng 5,5 3 3 0", + "1": "3 fng 5,5 3 3 1", + "2": "3 fng 5,5 3 3 2", + "3": "3 fng 5,5 3 3 3", + "4": "3 fng 5,5 3 3 4", + "5": "3 fng 5,5 3 3 5", plus: "3 dplus 7 3 3", "+": "3 dplus 7 3 3", - accent: "3 accent 7 4 4", - ">": "3 accent 7 4 4", + 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 10 5 5", + 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", - dacapo: "3 dacs 16 20 20 Da Capo", - dacoda: "3 dacs 16 20 20 Da Coda", - "D.C.": "3 dcap 16 10 10", - "D.S.": "3 dsgn 16 10 10", - "D.C.alcoda": "3 dacs 16 38 38 D.C. al Coda", - "D.S.alcoda": "3 dacs 16 38 38 D.S. al Coda", - "D.C.alfine": "3 dacs 16 38 38 D.C. al Fine", - "D.S.alfine": "3 dacs 16 38 38 D.S. al Fine", - fine: "3 dacs 16 10 10 Fine", - turn: "3 turn 10 0 5", + turn: "3 turn 5,5 6 6", "trill(": "3 ltr 8 4 0", "trill)": "3 ltr 8 4 0", - f: "6 f 18 1 7", - ff: "6 ff 18 2 10", - fff: "6 fff 18 4 13", - ffff: "6 ffff 18 6 16", - mf: "6 mf 18 6 13", - mp: "6 mp 18 6 16", - p: "6 p 18 2 8", - pp: "6 pp 18 5 14", - ppp: "6 ppp 18 8 20", - pppp: "6 pppp 18 10 25", - pralltriller: "3 umrd 10 5 5", - sfz: "6 sfz 18 4 10", + "8va(": "5 8va 12 6 6", + "8va)": "5 8va 12 6 6", + "8vb(": "7 8vb 12 6 6", + "8vb)": "7 8vb 12 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", + 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: "5 hld 12 7 7", + fine: "5 dacs 16 10 10 Fine", + invertedfermata: "7 hld 12 7 7", + segno: "5 sgno 22 8 8", + f: "6 f 18 4 4", + ff: "6 ff 18 6 6", + fff: "6 fff 18 9 9", + ffff: "6 ffff 18 12 12", + mf: "6 mf 18 9 9", + mp: "6 mp 18 10 10", + p: "6 p 18 5 5", + pp: "6 pp 18 9 9", + ppp: "6 ppp 18 13 13", + pppp: "6 pppp 18 17 17", + pralltriller: "3 umrd 4,4 5 5", + sfz: "6 sfz 18 8 8", ped: "4 ped 18 6 10", "ped-up": "4 pedoff 12 4 4", "ped(": "4 lped 20 5 5", "ped)": "4 lped 20 5 5", "crescendo(": "6 cresc 18 0 0", @@ -134,18 +146,10 @@ ">)": "6 dim 18 0 0", "-(": "8 gliss 0 0 0", "-)": "8 gliss 0 0 0", "~(": "8 glisq 0 0 0", "~)": "8 glisq 0 0 0", - "8va(": "3 8va 10 0 0", - "8va)": "3 8va 10 0 0", - "8vb(": "4 8vb 10 0 0", - "8vb)": "4 8vb 10 0 0", - "15ma(": "3 15ma 10 0 0", - "15ma)": "3 15ma 10 0 0", - "15mb(": "4 15mb 10 0 0", - "15mb)": "4 15mb 10 0 0", // internal // color: "10 0 0 0 0", invisible: "32 0 0 0 0", beamon: "33 0 0 0 0", trem1: "34 0 0 0 0", @@ -168,23 +172,38 @@ sacc3: "3 sacc3 6,5 4 4", sacc1: "3 sacc1 6,4 4 4", courtesy: "43 0 0 0 0", "cacc-1": "3 cacc-1 0 0 0", cacc3: "3 cacc3 0 0 0", - cacc1: "3 cacc1 0 0 0"}, + cacc1: "3 cacc1 0 0 0", + "tie(": "44 0 0 0 0", + "tie)": "44 0 0 0 0"}, // types of decoration per function - f_near = [true, true, true], - f_note = [false, false, false, true, true, true, false, false, true], - f_staff = [false, false, false, false, false, false, true, true] + f_near = [ + d_near, // 0 - near the note + d_slide, // 1 + d_arp // 2 + ], + f_note = [ + null, null, null, + d_upstaff, // 3 - tied to note + 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 // 7 (below the staff) + ] /* -- get the max/min vertical offset -- */ function y_get(st, up, x, w) { var y, p_staff = staff_tb[st], - i = (x / realwidth * YSTEP) | 0, - j = ((x + w) / realwidth * YSTEP) | 0 + i = (x / 2) | 0, + j = ((x + w) / 2) | 0 if (i < 0) i = 0 if (j >= YSTEP) { j = YSTEP - 1 @@ -209,13 +228,13 @@ return y } /* -- adjust the vertical offsets -- */ function y_set(st, up, x, w, y) { - var p_staff = staff_tb[st], - i = (x / realwidth * YSTEP) | 0, - j = ((x + w) / realwidth * YSTEP) | 0 + var p_staff = staff_tb[st], + i = (x / 2) | 0, + j = ((x + w) / 2) | 0 /* (may occur when annotation on 'y' at start of an empty staff) */ if (i < 0) i = 0 if (j >= YSTEP) { @@ -236,41 +255,55 @@ i++ } } } -/* -- get the staff position of the dynamic and volume marks -- */ -function up_p(s, pos) { - switch (pos) { +// get the staff position +// - of the ornements +function up3(s, pos) { + switch (pos & 0x07) { + case C.SL_ABOVE: + return 1 // true + case C.SL_BELOW: + return 0 // false + } + if (s.multi) + return s.multi > 0 + return 1 // true +} // up3() + +// - of the dynamic and volume marks +function up6(s, pos) { + switch (pos & 0x07) { case C.SL_ABOVE: return true case C.SL_BELOW: return false } - if (s.multi && s.multi != 0) + if (s.multi) return s.multi > 0 if (!s.p_v.have_ly) return false /* above if the lyrics are below the staff */ - return s.pos.voc != C.SL_ABOVE + return (s.pos.voc & 0x07) != C.SL_ABOVE } /* -- drawing functions -- */ /* 2: special case for arpeggio */ function d_arp(de) { var m, h, dx, s = de.s, dd = de.dd, - xc = 5 + xc = dd.wr if (s.type == C.NOTE) { for (m = 0; m <= s.nhd; m++) { if (s.notes[m].acc) { - dx = 5 + s.notes[m].shac + dx = s.notes[m].shac } else { - dx = 6 - s.notes[m].shhd + dx = 1 - s.notes[m].shhd switch (s.head) { case C.SQUARE: dx += 3.5 break case C.OVALBARS: @@ -290,44 +323,55 @@ de.has_val = true; de.val = h; // de.x = s.x - xc; de.x -= xc; - de.y = 3 * (s.notes[0].pit - 18) - 3 + 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 s, dd, dd2, up, x, dx, x2, i, - s2 = de.s, - de2 = de.start, /* start of the deco */ - de2_prev, de_next; - - s = de2.s; - x = s.x + 3; - i = de2.ix - if (i > 0) - de2_prev = a_de[i - 1]; + 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; - up = up_p(s2, s2.pos.dyn) + 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 the left - if (de2_prev && de2_prev.s == s - && ((de.up && !de2_prev.up) - || (!de.up && de2_prev.up))) { - dd2 = de2_prev.dd - if (f_staff[dd2.func]) { // if dynamic mark - x2 = de2_prev.x + de2_prev.val + 4 + // 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 @@ -335,20 +379,30 @@ x = de.x - 20 - 3; dx = 20 } } else { - // shift the ending point if any dynamic mark on the right - x2 = s2.x; - de_next = a_de[de.ix + 1] - if (de_next - && de_next.s == s - && ((de.up && !de_next.up) - || (!de.up && de_next.up))) { - dd2 = de_next.dd - if (f_staff[dd2.func]) // if dynamic mark - x2 -= 5 + // 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 @@ -356,14 +410,14 @@ } de.val = dx; de.x = x; de.y = y_get(de.st, up, x, dx) - if (!up) { - dd = de.dd; + 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) { @@ -374,33 +428,33 @@ if (dd.str) { // annotation like decoration // de.x = s.x; // de.y = s.y; return } - if (s.multi) + 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 > -6 && y < 30) { + y = (((y + 9) / 6) | 0) * 6 - 6 // between lines + } if (up) { - y = s.ymx | 0 - } else if (dd.name[0] == 'w') { + y += dd.hd + s.ymx = y + dd.h + } else if (dd.name[0] == 'w') { // wedge (no descent) de.inv = true - y = s.ymn + y -= dd.h + s.ymn = y } else { - y = s.ymn - dd.h - } - if (y > -6 && y < 24) { - if (up) - y += 3; - y = (((y + 6) / 6) | 0) * 6 - 6 /* between lines */ - } - if (up) - s.ymx = y + dd.h - else if (dd.name[0] == 'w') // wedge - s.ymn = y - dd.h - else - s.ymn = y + y -= dd.h + s.ymn = y - dd.hd + } de.y = y if (s.type == C.NOTE) de.x += s.notes[s.stem >= 0 ? 0 : s.nhd].shhd if (dd.name[0] == 'd' /* if dot decoration */ && s.nflags >= -1) { /* on stem */ @@ -422,20 +476,26 @@ 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 = up_p(s, s.pos.vol) + up = up6(s, de.pos) if (up) de.up = true; - x = s.x - dd.wl + 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))) { @@ -450,10 +510,12 @@ de.x = x; de.y = y_get(s.st, up, x, 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) { @@ -503,21 +565,30 @@ if (de2.val < 8) de2.val = 8 } de.st = st - if (dd.func != 4) { // if not below - switch (dd.glyph) { - case "8va": - case "15ma": - up = 1 - break - default: - up = s2.multi >= 0 - break - } - } + 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 + if (de.defl.noen) { /* if no decoration end */ w = de.x - x if (w < 20) { x = de.x - 20 - 3; w = 20 @@ -529,11 +600,10 @@ if (w < 10) { x -= 10 - w w = 10 } } - dd = de.dd; y = y_get(st, up, x - dd.wl - 5, w) if (up) { tmp = staff_tb[s.st].topbar + 2 if (y < tmp) y = tmp @@ -562,130 +632,111 @@ de.lden = false; de.has_val = true; de.val = w; de.x = x; de.y = y + de.up = up if (up) y += dd.h; y_set(st, up, x, w, y) if (up) s.ymx = s2.ymx = y else s.ymn = s2.ymn = y } -/* 3, 4: above (or below) the staff */ +/* 3, 4, 5, 7: above (or below) the staff */ function d_upstaff(de) { // don't treat here the long decorations if (de.ldst) // if long deco start return if (de.start) { // if long decoration d_trill(de) return } - var yc, up, inv, - s = de.s, - dd = de.dd, -// x = s.x, + + var y, up, inv, + s = de.s, + dd = de.dd, x = de.x, - w = dd.wl + dd.wr, - stafft = staff_tb[s.st].topbar + 2, - staffb = staff_tb[s.st].botbar - 2 - - if (s.nhd) - x += s.notes[s.stem >= 0 ? 0 : s.nhd].shhd; - up = -1 - if (dd.func == 4) { // below - up = 0 - } else if (s.pos) { - switch (s.pos.orn) { - case C.SL_ABOVE: - up = 1 - break - case C.SL_BELOW: - up = 0 - break - } - } - + w = dd.wl + dd.wr, + stafft = staff_tb[s.st].topbar + 2 + dd.hd, + staffb = staff_tb[s.st].botbar - 2 - dd.h + + // glyphs inside the staff switch (dd.glyph) { - case "accent": - case "roll": - if (!up - || (up < 0 - && (s.multi < 0 - || (!s.multi && s.stem > 0)))) { - yc = y_get(s.st, false, s.x - dd.wl, w) - 2 - if (yc > staffb) - yc = staffb; - yc -= dd.h; - y_set(s.st, false, s.x, 0, yc); - inv = true; - s.ymn = yc - } else { - yc = y_get(s.st, true, s.x - dd.wl, w) + 2 - if (yc < stafft) - yc = stafft; - y_set(s.st, true, s.x - dd.wl, w, yc + dd.h); - s.ymx = yc + dd.h - } - break case "brth": + case "caes": case "lphr": case "mphr": case "sphr": - yc = stafft + 1 - if (dd.glyph == "brth" && yc < s.ymx) - yc = s.ymx - for (s = s.ts_next; s; s = s.ts_next) - if (s.seqst) - break - x += ((s ? s.x : realwidth) - x) * .45 - break - default: - if (dd.name.indexOf("invert") == 0) - inv = true - if (dd.name != "invertedfermata" - && (up > 0 - || (up < 0 && s.multi >= 0))) { - yc = y_get(s.st, true, s.x - dd.wl, w) + 2 + dd.hd - if (yc < stafft) - yc = stafft; - y_set(s.st, true, s.x - dd.wl, w, yc + dd.h); - s.ymx = yc + dd.h - } else { - yc = y_get(s.st, false, s.x - dd.wl, w) - 3 + dd.hd - if (yc > staffb) - yc = staffb; - yc -= dd.h; - y_set(s.st, false, s.x - dd.wl, w, yc - dd.hd) - if (dd.name == "fermata") - inv = true; - s.ymn = yc - } - break - } - if (inv) { - yc += dd.h; - de.inv = true - } - de.x = x; - de.y = yc -} - -/* deco function table */ -var func_tb = [ - d_near, /* 0 - near the note */ - d_slide, /* 1 */ - d_arp, /* 2 */ - d_upstaff, /* 3 - tied to note */ - d_upstaff, /* 4 (below the staff) */ - d_trill, /* 5 */ - d_pf, /* 6 - tied to staff (dynamic marks) */ - d_cresc /* 7 */ -] + case "short": + case "tick": + y = stafft + if (s.type == C.BAR) { + s.invis = 1 + } else { + 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 + } + de.x = x + de.y = y + 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 '>': + y = de.y + break + } + 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") + de.inv = 1 + s.ymn = y - dd.hd + } + } + + if (dd.func != 6) { // if not d_pf + if (y >= stafft) + y_set(s.st, 1, x - dd.wl, w, y + dd.h) + else if (y <= staffb) + y_set(s.st, 0, x - dd.wl, w, y - dd.hd) + } + + de.x = x; + de.y = y +} // add a decoration /* syntax: * %%deco [] * "" may be followed by "," (descent) @@ -694,13 +745,21 @@ var dv = param.match(/(\S*)\s+(.*)/); decos[dv[1]] = dv[2] } // define a decoration -function deco_def(nm) { - var a, dd, dd2, name2, c, i, elts, str, hd, - text = decos[nm] +// nm is the name of the decoration +// nmd is the name of the definition in the table 'decos' +function deco_def(nm, nmd) { + if (!nmd) + nmd = nm + var a, dd, dd2, nm2, c, i, elts, str, hd, + text = decos[nmd] + + // check if a long decoration with number + if (!text && /\d[()]$/.test(nmd)) + text = decos[nmd.replace(/\d/, '')] if (!text) { if (cfmt.decoerr) error(1, null, "Unknown decoration '$1'", nm) return //undefined @@ -721,18 +780,18 @@ if (isNaN(c_func)) { error(1, null, "%%deco: bad C function value '$1'", a[1]) return //undefined } if (c_func > 10 - && (c_func < 32 || c_func > 43)) { + && (c_func < 32 || c_func > 44)) { error(1, null, "%%deco: bad C function index '$1'", c_func) return //undefined } - if (c_func == 5) // old !trill(! - c_func = 3 - if (c_func == 7) // old !cresc(! - c_func = 6 +// if (c_func == 5) // old !trill(! +// c_func = 3 +// if (c_func == 7) // old !cresc(! +// c_func = 6 if (h.indexOf(',') > 0) { h = h.split(',') hd = h[1] h = h[0] @@ -752,197 +811,261 @@ } dd_tb[nm] = dd } /* set the values */ - dd.func = dd.name.indexOf("head-") == 0 ? 9 : c_func; + dd.func = nm.indexOf("head-") == 0 ? 9 : c_func; dd.glyph = a[2]; dd.h = Number(h) dd.hd = Number(hd) dd.wl = wl; 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.-]+);?/)) { + error(1, null, "%%deco: bad position '$1'", str) + return + } dd.str = str } /* compatibility */ if (dd.func == 6 && dd.str == undefined) - dd.str = dd.name + dd.str = nm // link the start and end of long decorations - c = dd.name.slice(-1) + c = nm.slice(-1) if (c == '(' || - (c == ')' && dd.name.indexOf('(') < 0)) { + (c == ')' && nm.indexOf('(') < 0)) { // not (#) dd.str = null; // (no string) - name2 = dd.name.slice(0, -1) + (c == '(' ? ')' : '('); - dd2 = dd_tb[name2] + nm2 = nm.slice(0, -1) + (c == '(' ? ')' : '('); + dd2 = dd_tb[nm2] if (dd2) { if (c == '(') { dd.dd_en = dd2; dd2.dd_st = dd } else { dd.dd_st = dd2; dd2.dd_en = dd } - } else { - dd2 = deco_def(name2) - if (!dd2) - return //undefined + } + } + return dd +} + +// define a cross-voice tie +// @nm = decoration name +// @s = note symbol +// @nt1 = note +function do_ctie(nm, s, nt1) { + var nt2 = cross[nm], + nm2 = nm.slice(0, -1) + (nm.slice(-1) == '(' ? ')' : '(') + + if (nt2) { + error(1, s, "Conflict on !$1!", nm) + return + } + nt1.s = s + nt2 = cross[nm2] + if (!nt2) { + cross[nm] = nt1 // keep the start/end + return + } + if (nm.slice(-1) == ')') { + nt2 = nt1 + nt1 = cross[nm2] + } + cross[nm2] = null + if (nt1.midi != nt2.midi + || nt1.s.time + nt1.s.dur != nt2.s.time) { + error(1, s, "Bad tie") + } else { + nt1.tie_ty = C.SL_AUTO + nt1.tie_e = nt2 + nt2.tie_s = nt1 + nt1.s.ti1 = nt2.s.ti2 = true + } +} // do_ctie() + +// get/create the definition of a decoration +function get_dd(nm) { + var ty, p, + dd = dd_tb[nm] + + if (dd) + return dd + if ("<>^_@".indexOf(nm[0]) >= 0 // if position + && !/^([>^]|[<>]\d?[()])$/.test(nm)) { + ty = nm[0] + if (ty == '@') { + p = nm.match(/@([-\d]+),([-\d]+)/) + if (!p) { + error(1, s, "Bad position in !$1!", nm) + return + } + ty = p[0] + } + dd = deco_def(nm, nm.replace(ty, '')) + } else { + dd = deco_def(nm) + } + if (!dd) + return + if (ty) { + if (ty[0] == '@') { // if with x,y + dd.x = Number(p[1]) + dd.y = Number(p[2]) + ty = '@' } + dd.ty = ty } return dd -} +} // get_dd() /* -- convert the decorations -- */ -function deco_cnv(a_dcn, s, prev) { - var i, j, dd, dcn, note, - nd = a_dcn.length - - for (i = 0; i < nd; i++) { - dcn = a_dcn[i]; - dd = dd_tb[dcn] - if (!dd) { - dd = deco_def(dcn) - if (!dd) - continue - } +function deco_cnv(s, prev) { + var j, dd, nm, note, s1, court + + while (1) { + nm = a_dcn.shift() + if (!nm) + break + dd = get_dd(nm) + if (!dd) + continue /* special decorations */ switch (dd.func) { case 0: // near - if (s.type == C.BAR && dd.name == "dot") { + if (s.type == C.BAR && nm == "dot") { s.bar_dotted = true - break + continue } // fall thru case 1: // slide case 2: // arp // if (s.type != C.NOTE && s.type != C.REST) { if (!s.notes) { - error(1, s, - errs.must_note_rest, dd.name) + error(1, s, errs.must_note_rest, nm) continue } break case 8: // gliss if (s.type != C.NOTE) { - error(1, s, - errs.must_note, dd.name) + error(1, s, errs.must_note, nm) continue } note = s.notes[s.nhd] // move to the upper note of the chord - if (!note.a_dcn) - note.a_dcn = [] - note.a_dcn.push(dd.name) + if (!note.a_dd) + note.a_dd = [] + note.a_dd.push(dd) continue case 9: // alternate head if (!s.notes) { - error(1, s, - errs.must_note_rest, dd.name) + error(1, s, errs.must_note_rest, nm) continue } // move the alternate head of the chord to the notes for (j = 0; j <= s.nhd; j++) { note = s.notes[j] - if (!note.a_dcn) - note.a_dcn = [] - note.a_dcn.push(dd.name) + note.invis = true + if (!note.a_dd) + note.a_dd = [] + note.a_dd.push(dd) } continue - default: - break case 10: /* color */ if (s.notes) { for (j = 0; j <= s.nhd; j++) - s.notes[j].color = dd.name + s.notes[j].color = nm } else { - s.color = dd.name + s.color = nm } - continue + break case 32: /* invisible */ s.invis = true - continue + break case 33: /* beamon */ if (s.type != C.BAR) { error(1, s, "!beamon! must be on a bar") continue } s.beam_on = true - continue + break case 34: /* trem1..trem4 */ if (s.type != C.NOTE || !prev || prev.type != C.NOTE || s.dur != prev.dur) { error(1, s, "!$1! must be on the last of a couple of notes", - dd.name) + nm) continue } s.trem2 = true; s.beam_end = true; s.beam_st = false; prev.beam_st = true; prev.beam_end = false; - s.ntrem = prev.ntrem = Number(dd.name[4]); + s.ntrem = prev.ntrem = Number(nm[4]); for (j = 0; j <= s.nhd; j++) s.notes[j].dur *= 2; for (j = 0; j <= prev.nhd; j++) prev.notes[j].dur *= 2 - continue + break case 35: /* xstem */ if (s.type != C.NOTE) { - error(1, s, errs.must_note, dd.name) + error(1, s, errs.must_note, nm) continue } s.xstem = true; - continue + break case 36: /* beambr1 / beambr2 */ if (s.type != C.NOTE) { - error(1, s, errs.must_note, dd.name) + error(1, s, errs.must_note, nm) continue } - if (dd.name[6] == '1') + if (nm[6] == '1') s.beam_br1 = true else s.beam_br2 = true - continue + break case 37: /* rbstop */ s.rbstop = 1 // open - continue + break case 38: /* /, // and /// = tremolo */ if (s.type != C.NOTE) { - error(1, s, errs.must_note, dd.name) + error(1, s, errs.must_note, nm) continue } s.trem1 = true; - s.ntrem = dd.name.length /* 1, 2 or 3 */ - continue + s.ntrem = nm.length /* 1, 2 or 3 */ + break case 39: /* beam-accel/beam-rall */ if (s.type != C.NOTE) { - error(1, s, errs.must_note, dd.name) + error(1, s, errs.must_note, nm) continue } - s.feathered_beam = dd.name[5] == 'a' ? 1 : -1; - continue + s.feathered_beam = nm[5] == 'a' ? 1 : -1; + break case 40: /* stemless */ s.stemless = true - continue + break case 41: /* rbend */ s.rbstop = 2 // with end - continue + break case 42: // editorial if (!s.notes[0].acc) continue - dcn = "sacc" + s.notes[0].acc.toString() // small accidental - dd = dd_tb[dcn] + nm = "sacc" + s.notes[0].acc.toString() // small accidental + dd = dd_tb[nm] if (!dd) { - dd = deco_def(dcn) + dd = deco_def(nm) if (!dd) { error(1, s, errs.bad_val, "!editorial!") continue } } @@ -951,22 +1074,78 @@ break case 43: // courtesy j = curvoice.acc[s.notes[0].pit + 19] if (s.notes[0].acc || !j) continue - if (!s.notes[0].a_dcn) - s.notes[0].a_dcn = [] - s.notes[0].a_dcn.push("cacc" + j) + court = 1 // defer + break + case 44: // cross-voice ties + do_ctie(nm, s, s.notes[0]) // (only one note for now) continue +// default: +// break } // add the decoration in the symbol if (!s.a_dd) s.a_dd = [] s.a_dd.push(dd) } + // handle the possible courtesy accidental + if (court) { + a_dcn.push("cacc" + j) + dh_cnv(s, s.notes[0]) + } } + +// -- convert head decorations -- +// The decorations are in the global array a_dcn +function dh_cnv(s, nt) { + var k, nm, dd + + while (1) { + nm = a_dcn.shift() + if (!nm) + break + dd = get_dd(nm) + if (!dd) + continue + + switch (dd.func) { + case 0: + case 1: + case 3: + case 4: + case 8: // gliss + break + default: +// case 2: // arpeggio +// case 5: // trill +// case 7: // d_cresc + error(1, s, "Cannot have !$1! on a head", nm) + continue + case 9: // head replacement + case 32: // invisible + nt.invis = true + break + case 10: // color + nt.color = nm + continue + case 40: // stemless chord (abcm2ps behaviour) + s.stemless = true + continue + case 44: // cross-voice ties + do_ctie(nm, s, nt) + continue + } + + // add the decoration in the note + if (!nt.a_dd) + nt.a_dd = [] + nt.a_dd.push(dd) + } +} // dh_cnv() /* -- update the x position of a decoration -- */ // used to center the rests function deco_update(s, dx) { var i, de, @@ -979,11 +1158,11 @@ } } /* -- adjust the symbol width -- */ function deco_width(s) { - var dd, i, + var dd, i, w, wl = 0, a_dd = s.a_dd, nd = a_dd.length for (i = 0; i < nd; i++) { @@ -1005,36 +1184,65 @@ case "sphr": if (s.wr < 20) s.wr = 20 break } + // fall thru + default: + switch (dd.ty) { + case '<': + w = dd.wl + dd.wr + 6 + if (wl < w) + wl = w + break + case '>': + w = dd.wl + dd.wr + 8 + if (s.wr < w) + s.wr = w + break + } break } } - if (wl != 0 && s.prev && s.prev.type == C.BAR) + if (wl && s.prev && s.prev.type == C.BAR) wl -= 3 return wl } + +// compute the width of decorations in chord +function deco_wch(nt) { + var i, w, dd, + wl = 0, + 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 + if (w > wl) + wl = w + } + } + return wl +} // deco_wch() /* -- draw the decorations -- */ /* (the staves are defined) */ Abc.prototype.draw_all_deco = function() { if (!a_de.length) return - var de, de2, dd, s, note, f, st, x, y, y2, ym, uf, i, str, a, + var de, dd, s, note, f, st, x, y, y2, ym, uf, i, str, a, new_de = [], ymid = [] - if (!cfmt.dynalign) { st = nstaff; y = staff_tb[st].y while (--st >= 0) { y2 = staff_tb[st].y; ymid[st] = (y + 24 + y2) * .5; y = y2 } - } while (1) { de = a_de.shift() if (!de) break @@ -1074,11 +1282,14 @@ if (note.shhd) x += note.shhd * stv_g.scale; /* center the dynamic marks between two staves */ /*fixme: KO when deco on other voice and same direction*/ - } else if (f_staff[dd.func] && !cfmt.dynalign + } else if (dd.func == 6 + && ((de.pos & C.SL_ALI_MSK) == C.SL_CENTER + || ((de.pos & C.SL_ALI_MSK) == 0 + && !s.fmt.dynalign)) && ((de.up && st > 0) || (!de.up && st < nstaff))) { if (de.up) ym = ymid[--st] else @@ -1102,10 +1313,12 @@ || (!de.up && y2 < ym)) { y = ym; // y_set(st, de.up, de.x, de.val, // (de.up ? y + dd.h : y) // - staff_tb[st].y) + if (stv_g.scale != 1) + y += stv_g.dy / 2 } } } // check if user JS decoration @@ -1123,10 +1336,11 @@ // if (de.flags.grace) { // g_open(x, y, 0, .7, de.inv ? -.7 : 0); // x = y = 0 // } else if (de.inv) { + y = y + dd.h - dd.hd g_open(x, y, 0, 1, -1); x = y = 0 } if (de.has_val) { if (dd.func != 2 // if not !arpeggio! @@ -1133,11 +1347,11 @@ || stv_g.st < 0) // or not staff scale // || voice_tb[s.v].scale != 1) 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.defl.noen) + 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.-]+);?/); @@ -1184,47 +1398,86 @@ } } /* -- create the deco elements, and treat the near ones -- */ function create_deco(s) { - var dd, k, l, pos, de, x, - nd = s.a_dd.length + var dd, k, pos, de, x, y, v, + nd = s.a_dd.length + + if (s.y == undefined) + s.y = 0 // (no y in measure bars) /*fixme:pb with decorations above the staff*/ for (k = 0; k < nd; k++) { dd = s.a_dd[k] - /* check if hidden */ + // adjust the position + x = s.x + y = s.y switch (dd.func) { default: + if (dd.func >= 10) + continue 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 */ pos = s.pos.dyn break } - if (pos == C.SL_HIDDEN) + + switch (dd.ty) { // explicit position + case '^': + pos = (pos & ~0x07) | C.SL_ABOVE + break + case '_': + pos = (pos & ~0x07) | C.SL_BELOW + break + case '<': + case '>': + pos = (pos & 0x07) | C.SL_CLOSE + if (dd.ty == '<') { + x -= dd.wr + 8 + if (s.notes[0].acc) + x -= 5.5 + } else { + x += dd.wl + 8 + } + y = 3 * (s.notes[0].pit - 18) + - (dd.h - dd.hd) / 2 + break + case '@': + x += dd.x + y += dd.y + break + } + + if ((pos & 0x07) == C.SL_HIDDEN) continue de = { s: s, dd: dd, st: s.st, ix: a_de.length, defl: {}, - x: s.x, - y: s.y, + x: x, + y: y, + pos: pos // dy: 0 } - if (s.type == C.BAR) + if (dd.name.indexOf("invert") == 0) + de.inv = 1 + if (s.type == C.BAR && !dd.ty) de.x -= s.wl / 2 - 2 a_de.push(de) if (dd.dd_en) { de.ldst = true } else if (dd.dd_st) { @@ -1231,70 +1484,51 @@ //fixme: pb with "()" de.lden = true; de.defl.nost = true } - if (!f_near[dd.func]) /* if not near the note */ - continue - func_tb[dd.func](de) + if (f_near[dd.func]) + f_near[dd.func](de) } } // create_deco() // create the decorations of note heads function create_dh(s, m) { - var f, str, de, uf, k, dcn, dd, - note = s.notes[m], - nd = note.a_dcn.length + var de, k, dd, + note = s.notes[m], + nd = note.a_dd.length, + x = s.x for (k = 0; k < nd; k++) { - dcn = note.a_dcn[k]; - dd = dd_tb[dcn] - if (!dd) { - dd = deco_def(dcn) - if (!dd) - continue - } - - switch (dd.func) { - case 0: - case 1: - case 3: - case 4: - case 8: // gliss - break - default: -// case 2: // arpeggio -// case 5: // trill -// case 7: // d_cresc - error(1, null, "Cannot have !$1! on a head", dd.name) - continue - case 9: // head replacement - note.invis = true - break - case 10: // color - note.color = dd.name - continue - case 32: // invisible - note.invis = true - continue - case 40: // stemless chord (abcm2ps behaviour) - s.stemless = true - continue - } + dd = note.a_dd[k] //fixme: check if hidden? de = { s: s, dd: dd, st: s.st, m: m, ix: 0, defl: {}, - x: s.x, - y: 3 * (note.pit - 18), + x: x, + y: 3 * (note.pit - 18) // 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 == '<') + de.x -= dd.wr + 8 + else if (dd.ty == '>') + de.x += dd.wr + 8 + } + } + a_de.push(de) if (dd.dd_en) { de.ldst = true } else if (dd.dd_st) { de.lden = true; @@ -1303,15 +1537,17 @@ } } // create_dh() // create all decorations of a note (chord and heads) function create_all(s) { + if (s.invis && s.play) // play sequence: no decoration + return if (s.a_dd) create_deco(s) if (s.notes) { for (var m = 0; m < s.notes.length; m++) { - if (s.notes[m].a_dcn) + if (s.notes[m].a_dd) create_dh(s, m) } } } // create_all() @@ -1350,10 +1586,11 @@ st: de.st, dd: dd2, ix: a_de.length - 1, x: realwidth - 6, y: s.y, + cont: true, // keep for next line lden: true, defl: { noen: true } } @@ -1364,15 +1601,17 @@ a_de.push(de2) } de2.start = de; de2.defl.nost = de.defl.nost - // handle same decoration type at a same time - if (i > 0 + // handle same numbered decoration at a same time + j = dd.name.slice(-1) + if (j >= '0' && j <= '9' + && i > 0 && a_de[i - 1].s.time == de.s.time - && a_de[i - 1].dd.name.slice(0, dd.name.length - 1) == - dd.name.slice(0, dd.name.length - 1)) + && a_de[i - 1].dd.name.slice(0, -2) == + dd.name.slice(0, -2)) de2.prev = a_de[i - 1] } // add starting decorations for (i = 0; i < n_de; i++) { @@ -1442,11 +1681,11 @@ de = a_de[i]; dd = de.dd; f = dd.func if (f_note[f] && de.m == undefined) - func_tb[f](de) + f_note[f](de) } } // -- define the music elements tied to the staff -- // - decoration tied to the staves @@ -1461,11 +1700,11 @@ /* draw the repeat brackets */ function draw_repbra(p_voice) { var s, s1, x, y, y2, i, p, w, wh, first_repeat; - /* search the max y offset */ + // search the max y offset of the line y = staff_tb[p_voice.st].topbar + 25 // 20 (vert bar) + 5 (room) for (s = p_voice.sym; s; s = s.next) { if (s.type != C.BAR) continue if (!s.rbstart || s.norepbra) @@ -1483,15 +1722,25 @@ break s = s.next if (s.rbstop) break } - y2 = y_get(p_voice.st, true, s1.x, s.x - s1.x) + y2 = y_get(p_voice.st, true, s1.x, s.x - s1.x) + 2 if (y < y2) y = y2 - /* have room for the repeat numbers */ + // have room for the vertical lines and the repeat numbers + if (s1.rbstart == 2) { + y2 = y_get(p_voice.st, true, s1.x, 3) + 20 + if (y < y2) + y = y2 + } + if (s.rbstop == 2) { + y2 = y_get(p_voice.st, true, s.x - 3, 3) + 20 + if (y < y2) + y = y2 + } if (s1.text) { wh = strwh(s1.text); y2 = y_get(p_voice.st, true, s1.x + 4, wh[0]) + wh[1] if (y < y2) @@ -1548,16 +1797,13 @@ w = (s.x - x - w) // / staff_tb[p_voice.st].staffscale; if (!s.next // 2nd ending at end of line && !s.rbstop && !p_voice.bar_start) { // continue on next line - p_voice.bar_start = clone(s); - p_voice.bar_start.type = C.BAR; + p_voice.bar_start = _bar(s) p_voice.bar_start.bar_type = "" - delete p_voice.bar_start.text; p_voice.bar_start.rbstart = 1 - delete p_voice.bar_start.a_gch } if (s1.text) xy_str(x + 4, y2 - gene.curfont.size - 3, s1.text); xypath(x, y2); @@ -1586,14 +1832,16 @@ if (!dd) // if error continue if (!f_staff[dd.func] /* if not tied to the staff */ || de.m != undefined) // or head decoration continue - func_tb[dd.func](de) + f_staff[dd.func](de) if (dd.dd_en) // if start continue - if (cfmt.dynalign) { + 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) minmax[de.st].ymax = de.y } else { if (de.y < minmax[de.st].ymin) @@ -1609,11 +1857,14 @@ if (!dd) // if error continue if (dd.dd_en // if start || !f_staff[dd.func]) continue - if (cfmt.dynalign) { + 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 if (de.up) y = minmax[de.st].ymax else y = minmax[de.st].ymin; de.y = y @@ -1682,19 +1933,17 @@ break } // don't display the number twice if (s.type != C.BAR || !s.bar_num) { - if (s.prev) - s = s.prev; any_nb = true; w = w0 if (bar_num >= 10) w *= bar_num >= 100 ? 3 : 2 if (gene.curfont.pad) w += gene.curfont.pad * 2 - x = s.x + s.wr + 1 // if clef, shift a bit + x = s.x - s.wl + 2 // if clef, shift a bit y = y_get(st, true, x, w) if (y < staff_tb[st].topbar + 6) y = staff_tb[st].topbar + 6; y += 2 + gene.curfont.pad xy_str(x, y, bar_num.toString()) @@ -1734,14 +1983,14 @@ w = w0 if (bar_num >= 10) w *= bar_num >= 100 ? 3 : 2 if (gene.curfont.pad) w += gene.curfont.pad * 2 - x = s.x - w * (s.text ? .6 : .4) + x = s.x y = y_get(st, true, x, w) - if (y < staff_tb[st].topbar + 3) - y = staff_tb[st].topbar + 3 + if (y < staff_tb[st].topbar + 6) + y = staff_tb[st].topbar + 6 if (s.next.type == C.NOTE) { if (s.next.stem > 0) { if (y < s.next.ys - gene.curfont.size) y = s.next.ys - gene.curfont.size } else { @@ -1762,11 +2011,11 @@ } /* -- draw the parts and the tempo information -- */ /* (the staves are being defined) */ function draw_partempo(st, top) { - var s, some_part, some_tempo, h, w, y, + var s, s2, some_part, some_tempo, h, w, y, dy = 0, /* put the tempo indication at top */ ht = 0 /* get the minimal y offset */ var ymin = staff_tb[st].topbar + 8, @@ -1823,43 +2072,47 @@ /* then, put the parts */ /*fixme: should reduce vertical space if parts don't overlap tempo...*/ ymin = staff_tb[st].topbar + 6 for (s = tsfirst; s; s = s.ts_next) { - if (s.type != C.PART || s.invis) + s2 = s.part + if (!s2 || s2.invis) continue if (!some_part) { some_part = s; set_font("parts"); h = gene.curfont.size + 2 + gene.curfont.pad * 2 } - w = strwh(s.text)[0]; - y = y_get(st, true, s.x - 10, w + 3) + if (s2.x == undefined) + s2.x = s.x - 10 + w = strwh(s2.text)[0] + y = y_get(st, true, s2.x, w + 3) if (ymin < y) ymin = y } if (some_part) { set_sscale(-1) - if (top + dy < ymin + h + ht) - dy = ymin + h + ht - top + ht += h + if (top + dy < ymin + ht) + dy = ymin + ht - top for (s = some_part; s; s = s.ts_next) { - if (s.type != C.PART || s.invis) - continue - s.x -= 10; - if (user.anno_start || user.anno_stop) { - w = strwh(s.text)[0]; - s.wl = 0; - s.wr = w; - s.ymn = -ht - h; - s.ymx = s.ymn + h; - anno_start(s) - } - xy_str(s.x, 2 - ht - h, s.text); - anno_stop(s) - while (s.ts_next && s.ts_next.type == C.PART) - s = s.ts_next - } - } - return dy + s2 = s.part + if (!s2 || s2.invis) + continue + 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.ymx = s2.ymn + h + anno_start(s2) + } + xy_str(s2.x, 2 - ht, s2.text) + anno_stop(s2) + } + } + return dy /= staff_tb[0].staffscale } Index: core/draw.js ================================================================== --- core/draw.js +++ core/draw.js @@ -1,8 +1,8 @@ // abc2svg - draw.js - draw functions // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2022 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -79,11 +79,11 @@ delete s.a_dd; delete s.tp s.notes = clone(s.notes) for (m = 0; m <= s.nhd; m++) { note = s.notes[m] = clone(s.notes[m]) - delete note.a_dcn + delete note.a_dd } return s } /* -- calculate a beam -- */ @@ -212,11 +212,11 @@ if (s == s2) break } // have flat beams on grace notes when asked - if (s.grace && cfmt.flatbeams) + if (s.grace && s1.fmt.flatbeams) a = 0 // if a note inside the beam is the closest to the beam, the beam is flat else if (!two_dir && notes >= 3 @@ -225,13 +225,16 @@ y = s1.ys + staff_tb[st].y if (a == undefined) a = (s2.ys + staff_tb[s2.st].y - y) / (s2.xs - s1.xs) - if (a != 0) - a = cfmt.beamslope * a / - (cfmt.beamslope + Math.abs(a)) // max steepness for beam + 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 b = (y + s2.ys + staff_tb[s2.st].y) / 2 - a * (s2.xs + s1.xs) / 2 /*fixme: have a look again*/ @@ -624,12 +627,10 @@ anno_stop(s1, 'beam') } /* -- draw the left side of the staves -- */ function draw_lstaff(x) { -// if (cfmt.alignbars) -// return var i, j, yb, h, fl, nst = cur_sy.nstaff, l = 0 /* -- draw a system brace or bracket -- */ @@ -726,11 +727,11 @@ var acc_nd = {} // cache of the microtonal accidentals /* -- draw an accidental -- */ function draw_acc(x, y, a) { - if (typeof a != "number") { // if microtone + if (typeof a == "object") { // if microtone var c, n = a[0], d = a[1] c = n + '_' + d @@ -784,11 +785,12 @@ hla = [], st = s.st, p_staff = staff_tb[st] // check if any helper line - if (!p_staff.hll) + if (!p_staff.hll + || s.invis) return // no helper line (no line) for (i = 0; i <= s.nhd; i++) { note = s.notes[i] if (!p_staff.hlmap[note.pit - p_staff.hll]) hla.push([note.pit - 18, @@ -858,12 +860,10 @@ sharp2 = new Int8Array([12, -9, 12, -9, 12, -9]), flat1 = new Int8Array([9, -12, 9, -12, 9, -12]), flat2 = new Int8Array([-12, 9, -12, 9, -12, 9]) Abc.prototype.draw_keysig = function(x, s) { - if (s.k_none || s.k_play) - return var old_sf = s.k_old_sf, st = s.st, staffb = staff_tb[st].y, i, shift, p_seq, clef_ix = s.k_y_clef @@ -877,11 +877,11 @@ /* normal accidentals */ if (!s.k_a_acc) { /* put neutrals if 'accidental cancel' */ - if (cfmt.cancelkey || s.k_sf == 0) { + if (s.fmt.cancelkey || s.k_sf == 0) { /* when flats to sharps, or sharps to flats, */ if (s.k_sf == 0 || old_sf * s.k_sf < 0) { @@ -914,11 +914,11 @@ for (i = 0; i < s.k_sf; i++) { xygl(x, staffb + shift, "acc1"); shift += p_seq[i]; x += 5.5 } - if (cfmt.cancelkey && i < old_sf) { + if (s.fmt.cancelkey && i < old_sf) { x += 2 for (; i < old_sf; i++) { xygl(x, staffb + shift, "acc3"); shift += p_seq[i]; x += 5.5 @@ -933,11 +933,11 @@ for (i = 0; i > s.k_sf; i--) { xygl(x, staffb + shift, "acc-1"); shift += p_seq[-i]; x += 5.5 } - if (cfmt.cancelkey && i > old_sf) { + if (s.fmt.cancelkey && i > old_sf) { x += 2 for (; i > old_sf; i--) { xygl(x, staffb + shift, "acc3"); shift += p_seq[-i]; x += 5.5 @@ -958,47 +958,34 @@ for (i = 0; i < s.k_a_acc.length; i++) { acc = s.k_a_acc[i]; shift = (s.k_y_clef // clef shift + acc.pit - 18) * 3 + while (shift < -3) // let the accidentals inside the staff + shift += 21 + while (shift > 27) + shift -= 21 if (i != 0 && (shift > last_shift + 18 || shift < last_shift - 18)) x -= 5.5 // no clash else if (acc.acc != last_acc) x += 3; last_acc = acc.acc; s2.x = x s2.notes[0].pit = shift / 3 + 18; - self.draw_hl(s2) +// is this useful? +// s2.head = C.FULL +// s2.notes[0].shhd = 0 +// self.draw_hl(s2) last_shift = shift; draw_acc(x, staffb + shift, acc.acc) x += 5.5 } } } -/* -- convert the standard measure bars -- */ -function bar_cnv(bar_type) { - switch (bar_type) { - case "[": - case "[]": - return "" /* invisible */ - case "|:": - case "|::": - case "|:::": - return "[" + bar_type /* |::: -> [|::: */ - case ":|": - case "::|": - case ":::|": - return bar_type + "]" /* :..| -> :..|] */ - case "::": - return cfmt.dblrepbar /* :: -> double repeat bar */ - } - return bar_type -} - // output the measure repeat number function nrep_out(x, y, n) { y -= 3 if (n < 10) { xygl(x - 4, y, "mtr" + n) @@ -1005,59 +992,57 @@ } else { xygl(x - 10, y, "mtr" + ((n / 10) | 0)) xygl(x - 2, y, "mtr" + (n % 10)) } } // nrep_out() + +// if rest alone in the measure or measure repeat, +// change the head and center +function center_rest(s) { + var s2, x + + if (s.dur < C.BLEN * 2) + s.nflags = -2 // semibreve / whole + else if (s.dur < C.BLEN * 4) + s.nflags = -3 + else + s.nflags = -4 + s.dots = 0 + + /* don't use next/prev: there is no bar in voice overlay */ + s2 = s.ts_next + while (s2.time != s.time + s.dur + && s2.ts_next) + s2 = s2.ts_next + x = s2.x - s2.wl + s2 = s + while (!s2.seqst) + s2 = s2.ts_prev + s2 = s2.ts_prev + x = (x + s2.x + s2.wr) / 2 + + /* center the associated decorations */ + if (s.a_dd) + deco_update(s, x - s.x) + s.x = x +} // center_rest() /* -- draw a rest -- */ /* (the staves are defined) */ var rest_tb = [ "r128", "r64", "r32", "r16", "r8", "r4", "r2", "r1", "r0", "r00"] function draw_rest(s) { - var s2, i, j, x, y, yb, bx, - p_staff = staff_tb[s.st] - - // if rest alone in the measure or measure repeat, - // change the head and center - if (s.dur_orig == s.p_v.meter.wmeasure - || (s.rep_nb && s.rep_nb >= 0)) { - if (s.dur < C.BLEN * 2) - s.nflags = -2 // semibreve / whole - else if (s.dur < C.BLEN * 4) - s.nflags = -3 - else - s.nflags = -4; - s.dots = 0; - - /* don't use next/prev: there is no bar in voice overlay */ - s2 = s.ts_next - while (s2.time != s.time + s.dur - && s2.ts_next) - s2 = s2.ts_next - x = s2.x - s2.wl - s2 = s - while (!s2.seqst) - s2 = s2.ts_prev; - s2 = s2.ts_prev; - x = (x + s2.x + s2.wr) / 2 - - /* center the associated decorations */ - if (s.a_dd) - deco_update(s, x - s.x); - s.x = x - } else { - x = s.x - if (s.notes[0].shhd) - x += s.notes[0].shhd * stv_g.scale - } - if (s.invis) - return - - yb = p_staff.y // bottom of staff + var s2, i, j, y, bx, + p_staff = staff_tb[s.st], + yb = p_staff.y, // bottom of staff + x = s.x + + if (s.notes[0].shhd) + x += s.notes[0].shhd * stv_g.scale if (s.rep_nb) { set_sscale(s.st); anno_start(s); if (p_staff.stafflines == '|||||') @@ -1067,12 +1052,12 @@ if (s.rep_nb < 0) { xygl(x, yb, "srep") } else { xygl(x, yb, "mrep") if (s.rep_nb > 2 && s.v == cur_sy.top_voice - && cfmt.measrepnb > 0 - && !(s.rep_nb % cfmt.measrepnb)) + && s.fmt.measrepnb > 0 + && !(s.rep_nb % s.fmt.measrepnb)) nrep_out(x, yb + p_staff.topbar, s.rep_nb) } anno_a.push(s) return } @@ -1092,43 +1077,10 @@ // draw the rest if (!s.notes[0].invis) // if not head replacement xygl(x, y + yb, rest_tb[i]) - /* output ledger line(s) when greater than minim */ - if (i >= 6) { - j = y / 6 - switch (i) { - default: - switch (p_staff.stafflines[j + 1]) { - case '|': - case '[': - break - default: - set_hl(p_staff, j + 1, x, -7, 7) - break - } - if (i == 9) { /* longa */ - y -= 6; - j-- - } - break - case 7: /* semibreve */ - y += 6; - j++ - case 6: /* minim */ - break - } - switch (p_staff.stafflines[j]) { - case '|': - case '[': - break - default: - set_hl(p_staff, j, x, -7, 7) - break - } - } if (s.dots) { x += 8; y += yb + 3 j = s.dots i = (s.dur_orig / 12) >> ((5 - s.nflags) - j) @@ -1139,66 +1091,51 @@ } set_color(); anno_a.push(s) } -/* -- draw grace notes -- */ -/* (the staves are defined) */ -function draw_gracenotes(s) { - var yy, x0, y0, x1, y1, x2, y2, x3, y3, bet1, bet2, slur, - dy1, dy2, g, last, note, - bm = {} - - /* draw the notes */ -// bm.s2 = undefined /* (draw flags) */ - for (g = s.extra; g; g = g.next) { - if (g.beam_st && !g.beam_end) { - if (self.calculate_beam(bm, g)) - draw_beams(bm) - } - anno_start(g); - draw_note(g, !bm.s2) - if (g == bm.s2) - bm.s2 = null /* (draw flags again) */ - anno_a.push(s) - if (g.sls || g.sl2) - slur = true +// -- draw a multi-measure rest -- +// (the staves are defined) +function draw_mrest(s) { + var x1, x2, + p_st = staff_tb[s.st], + y = p_st.y + (p_st.topbar + p_st.botbar) / 2, + p = s.nmes.toString() + + if (!s.next) { + error(1, s, "Lack of bar after multi-measure rest") + return + } + set_scale(s) + x1 = s.prev.x + 20 + x2 = s.next.x - 20 + s.x = (x1 + x2) / 2 + anno_start(s) + out_XYAB('\n' + out_XYAB('A\n', + s.x, y + 22, m_gl(p)) + anno_a.push(s) +} // draw_mrest() + +function grace_slur(s) { + var yy, x0, y0, x3, y3, bet1, bet2, dy1, dy2, last, below, + so = s, + g = s.extra + + while (1) { if (!g.next) break /* (keep the last note) */ - } - - // if an acciaccatura, draw a bar - if (s.sappo) { - g = s.extra - if (!g.next) { /* if one note */ - x1 = 9; - y1 = g.stem > 0 ? 5 : -5 - } else { /* many notes */ - x1 = (g.next.x - g.x) * .5 + 4; - y1 = (g.ys + g.next.ys) * .5 - g.y - if (g.stem > 0) - y1 -= 1 - else - y1 += 1 - } - note = g.notes[g.stem < 0 ? 0 : g.nhd]; - out_acciac(x_head(g, note), y_head(g, note), - x1, y1, g.stem > 0) - } - - /* slur */ -//fixme: have a full key symbol in voice - if (s.p_v.ckey.k_bagpipe /* no slur when bagpipe */ - || !cfmt.graceslurs - || slur // explicit slur - || s.tie_s // some tie - || !s.next - || s.next.type != C.NOTE) - return + g = g.next + } last = g - if (((g.stem >= 0 || s.multi < 0) && g.notes[0].pit <= 28) - || g.notes[0].pit < 16) { // slur below + + below = ((g.stem >= 0 || s.multi < 0) && g.notes[0].pit <= 28) + || g.notes[0].pit < 16 + if (below) { yy = 127 for (g = s.extra; g; g = g.next) { if (g.y < yy) { yy = g.y; last = g @@ -1272,21 +1209,90 @@ x0 = last.x + 2; y0 = last.y + 4 } } - x1 = bet1 * x3 + (1 - bet1) * x0 - x0; - y1 = bet1 * y3 + (1 - bet1) * y0 - dy1 - y0; - x2 = bet2 * x3 + (1 - bet2) * x0 - x0; - y2 = bet2 * y3 + (1 - bet2) * y0 - dy2 - y0; - - anno_start(s, 'slur'); - xypath(x0, y0 + staff_tb[s.st].y); - output += 'c' + x1.toFixed(1) + ' ' + (-y1).toFixed(1) + - ' ' + x2.toFixed(1) + ' ' + (-y2).toFixed(1) + - ' ' + (x3 - x0).toFixed(1) + ' ' + (-y3 + y0).toFixed(1) + '"/>\n'; - anno_stop(s, 'slur') + so.slur = { + x0: x0, + y0: y0, + x1: bet1 * x3 + (1 - bet1) * x0 - x0, + y1: y0 - bet1 * y3 - (1 - bet1) * y0 + dy1, + x2: bet2 * x3 + (1 - bet2) * x0 - x0, + y2: y0 - bet2 * y3 - (1 - bet2) * y0 + dy2, + x3: x3 - x0, + y3: y0 - y3 + } + y0 -= so.slur.y1 + g = so.extra +//fixme: useless? +// y_set(s.st, !below, x0, x3 - x0, y0) + if (below) { + if (y0 < g.ymn) + g.ymn = y0 + } else { + if (y0 > g.ymx) + g.ymx = y0 + } +} // grace_slur() + +/* -- draw grace notes -- */ +/* (the staves are defined) */ +function draw_gracenotes(s) { + var x1, y1, + last, note, + bm = {}, + g = s.extra + + /* draw the notes */ +// bm.s2 = undefined /* (draw flags) */ + while (1) { + if (g.beam_st && !g.beam_end) { + if (self.calculate_beam(bm, g)) + draw_beams(bm) + } + anno_start(g) + draw_note(g, !bm.s2) + if (g == bm.s2) + bm.s2 = null /* (draw flags again) */ + anno_a.push(s) +// if (g.sls || g.sl2) +// slur = true + if (!g.next) + break /* (keep the last note) */ + g = g.next + } + last = g + + // if an acciaccatura, draw a bar + if (s.sappo) { + g = s.extra + if (!g.next) { /* if one note */ + x1 = 9 + y1 = g.stem > 0 ? 5 : -5 + } else { /* many notes */ + x1 = (g.next.x - g.x) * .5 + 4 + y1 = (g.ys + g.next.ys) * .5 - g.y + if (g.stem > 0) + y1 -= 1 + else + y1 += 1 + } + note = g.notes[g.stem < 0 ? 0 : g.nhd] + out_acciac(x_head(g, note), y_head(g, note), + x1, y1, g.stem > 0) + } + + /* slur */ + g = s.slur + if (g) { + anno_start(s, 'slur') + xypath(g.x0, g.y0 + staff_tb[s.st].y) + output += 'c' + g.x1.toFixed(1) + ' ' + g.y1.toFixed(1) + + ' ' + g.x2.toFixed(1) + ' ' + g.y2.toFixed(1) + + ' ' + g.x3.toFixed(1) + ' ' + g.y3.toFixed(1) + '"/>\n' + anno_stop(s, 'slur') + } } /* -- set the y offset of the dots -- */ function setdoty(s, y_tb) { var m, m1, y @@ -1332,15 +1338,16 @@ } /* -- draw m-th head with accidentals and dots -- */ /* (the staves are defined) */ // sets {x,y}_note -function draw_basic_note(x, s, m, y_tb) { +function draw_basic_note(s, m, y_tb) { var i, p, yy, dotx, doty, inv, old_color = false, note = s.notes[m], staffb = staff_tb[s.st].y, /* bottom of staff */ + x = s.x, y = 3 * (note.pit - 18), /* note height on staff */ shhd = note.shhd * stv_g.scale, x_note = x + shhd, y_note = y + staffb @@ -1454,25 +1461,21 @@ /* -- draw a note or a chord -- */ /* (the staves are defined) */ function draw_note(s, fl) { // draw flags - var s2, i, m, y, staffb, slen, c, nflags, - x, y, note, - y_tb = new Array(s.nhd + 1) + var s2, i, m, y, slen, c, nflags, + y_tb = new Array(s.nhd + 1), + note = s.notes[s.stem < 0 ? s.nhd : 0], // master note head + x = x_head(s, note), + y = y_head(s, note), + staffb = staff_tb[s.st].y if (s.dots) setdoty(s, y_tb) - note = s.notes[s.stem < 0 ? s.nhd : 0]; // master note head - x = x_head(s, note) - staffb = staff_tb[s.st].y - - self.draw_hl(s) - /* draw the stem and flags */ - y = y_head(s, note) if (!s.stemless) { slen = s.ys - s.y; nflags = s.nflags if (s.ntrem) nflags -= s.ntrem @@ -1484,11 +1487,11 @@ slen += 1 } out_stem(x, y, slen, s.grace) } else { /* stem and flags */ out_stem(x, y, slen, s.grace, - nflags, cfmt.straightflags) + nflags, s.fmt.straightflags) } } else if (s.xstem) { /* cross-staff stem */ s2 = s.ts_prev; slen = (s2.stem > 0 ? s2.y : s2.ys) - s.y; slen += staff_tb[s2.st].y - staffb; @@ -1515,13 +1518,12 @@ slen /= s.p_v.scale; out_trem(x1, staffb + slen, ntrem) } /* draw the note heads */ - x = s.x for (m = 0; m <= s.nhd; m++) - draw_basic_note(x, s, m, y_tb) + draw_basic_note(s, m, y_tb) } // find where to start a long decoration function prev_scut(s) { while (s.prev) { @@ -1725,23 +1727,21 @@ x1 += 5 if (k1.beam_end && k1.nflags >= -1 /* if with a stem */ //fixme: check if at end of tuplet && !k1.in_tuplet) { -// || k1.ys > y1 - 3)) { if (k1.nflags > 0) { x1 += 2; y1 = k1.ys - 3 } else { y1 = k1.ys - 6 } -// don't clash with decorations -// } else { -// y1 = k1.ys + 3 + } else { + y1 = k1.ys + 3 } -// } else { -// y1 = k1.y + 8 + } else { + y1 = k1.y + 8 } } else { if (k1.stem < 0) { x1 -= 1 if (k2.grace) { @@ -1754,15 +1754,15 @@ x1 += 2; y1 = k1.ys + 3 } else { y1 = k1.ys + 6 } -// } else { -// y1 = k1.ys - 3 + } else { + y1 = k1.ys - 3 } -// } else { -// y1 = k1.y - 8 + } else { + y1 = k1.y - 8 } } } } @@ -1776,29 +1776,28 @@ if (k2.stem > 0) { x2 += 1 if (k2.beam_st && k2.nflags >= -1 && !k2.in_tuplet) -// || k2.ys > y2 - 3)) y2 = k2.ys - 6 -// else -// y2 = k2.ys + 3 -// } else { -// y2 = k2.y + 8 + else + y2 = k2.ys + 3 + } else { + y2 = k2.y + 8 } } else { if (k2.stem < 0) { x2 -= 5 if (k2.beam_st && k2.nflags >= -1 && !k2.in_tuplet) // || k2.ys < y2 + 3)) y2 = k2.ys + 6 -// else -// y2 = k2.ys - 3 -// } else { -// y2 = k2.y - 8 + else + y2 = k2.ys - 3 + } else { + y2 = k2.y - 8 } } } } @@ -1959,11 +1958,11 @@ height = .8 * y } else { if (height > -.8 * y) height = -.8 * y } - height *= cfmt.slurheight; + height *= k1.fmt.slurheight; // anno_start(k1_o, 'slur'); slur_out(x1, y1, x2, y2, dir, height, ty & C.SL_DOTTED); // anno_stop(k1_o, 'slur'); @@ -1978,11 +1977,11 @@ 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) + if (k.st != upstaff || k.type == C.BAR) continue y = a * k.x + addy if (k.ymx < y) k.ymx = y else if (k.ymn > y) @@ -2170,15 +2169,15 @@ } /* -- draw a tuplet -- */ /* (the staves are not yet defined) */ /* (delayed output) */ -/* See http://moinejf.free.fr/abcm2ps-doc/tuplets.xhtml +/* 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, some_slur, - x1, x2, y1, y2, xm, ym, a, s0, yy, yx, dy, a, dir, r, + var s2, s3, g, upstaff, nb_only, + x1, x2, y1, y2, xm, ym, a, s0, yy, yx, dy, a, dir, r, w, tp = s1.tp.shift() // tuplet parameters if (!s1.tp.length) delete s1.tp // last tuplet @@ -2430,16 +2429,15 @@ for (s3 = s1; ; s3 = s3.next) { if (s3.st == upstaff) { yy = ym + (s3.x - xm) * a if (s3.ymx < yy) s3.ymx = yy - if (s3 == s2) - break - y_set(upstaff, true, s3.x, s3.next.x - s3.x, yy) - } else if (s3 == s2) { - break + w = s3.next.x - s3.x - 10 + y_set(upstaff, true, s3.x - w, w * 2, 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..*/ @@ -2469,15 +2467,19 @@ if (s0 > 0) { if (a < 0) a = 0 else if (a > s0) a = s0 + if (a > .35) + a = .35 } else { if (a > 0) a = 0 else if (a < s0) a = s0 + if (a < -.35) + a = -.35 } if (a * a < .1 * .1) a = 0 /* shift down the bracket if needed */ @@ -2503,16 +2505,16 @@ /* shift the slurs / decorations */ ym -= 2 for (s3 = s1; ; s3 = s3.next) { if (s3.st == upstaff) { - if (s3 == s2) - break yy = ym + (s3.x - xm) * a if (s3.ymn > yy) s3.ymn = yy; - y_set(upstaff, false, s3.x, s3.next.x - s3.x, yy) + w = s3.next.x - s3.x - 10 + if (s3.next) + y_set(upstaff, false, s3.x - w, w * 2, yy) } if (s3 == s2) break } } /* lower voice */ @@ -2528,14 +2530,17 @@ y_set(upstaff, true, xm - 3, 6, yy + 2) else y_set(upstaff, false, xm - 3, 6, yy) } -/* -- draw the ties between two notes/chords -- */ -function draw_note_ties(not1, job) { - var m, x1, x2, s, y, h, time, - not2 = not1.tie_n, +// -- draw a ties -- +function draw_tie(not1, not2, + job) { // -: start and end + // 1: no starting note + // 2: no ending note + // 3: no start for clef or staff change + var m, x1, s, y, h, time, p = job == 2 ? not1.pit : not2.pit, dir = (not1.tie_ty & 0x07) == C.SL_ABOVE ? 1 : -1, s1 = not1.s, st = s1.st, s2 = not2.s, @@ -2572,37 +2577,28 @@ } x2 += sh // * .6 } switch (job) { - case 0: - p = (not1.pit & 1) ? not1.pit : not2.pit + default: + if (p < not2.pit || dir < 0) + p = not1.pit break - case 3: /* clef or staff change */ + case 3: dir = -dir - // fall thru case 1: /* no starting note */ - x1 = s1.x + x1 = s2.prev ? (s2.prev.x + s2.wr) : s1.x + if (s1.st != s2.st) + st = s2.st + x1 += (x2 - x1) * .4 if (x1 > x2 - 20) x1 = x2 - 20 - p = not2.pit - st = s2.st - break -/* case 2: * no ending note */ - default: - if (s1 != s2) { - x2 -= s2.wl - if (s2.type == C.BAR) - x2 += 5 - } else { - time = s1.time + s1.dur - for (s = s1.ts_next; s; s = s.ts_next) -//(fixme: must check if the staff continues??) - if (s.time > time) - break - x2 = s ? s.x : realwidth - } + break + case 2: // no ending note + x2 = s1.next ? s1.next.x : realwidth + if (x2 != realwidth) + x2 -= (x2 - x1) * .4 if (x2 < x1 + 16) x2 = x1 + 16 break } if (x2 - x1 > 20) { @@ -2618,204 +2614,89 @@ || (dir < 0 && s1.dot_low))) x1 += 5 y = staff_tb[st].y + 3 * (p - 18) + /* 1.0 * */ dir - h = (.03 * (x2 - x1) + 16) * dir * cfmt.tieheight + h = (.03 * (x2 - x1) + 16) * dir * s1.fmt.tieheight // anno_start(k1, 'slur') slur_out(x1, y, x2, y, dir, h, not1.tie_ty & C.SL_DOTTED) // anno_stop(k1, 'slur') -} - -/* -- draw ties between neighboring notes/chords -- */ -function draw_ties(k1, k2, - job) { // 0: normal - // 1: no starting note - // 2: no ending note - // 3: no start for clef or staff change - var k3, i, j, not1, not3, time, pit, pit2, - mhead3 = [], - nh1 = k1.nhd - - /* half ties from last note in line or before new repeat */ - if (job == 2) { - for (i = 0; i <= nh1; i++) { - not1 = k1.notes[i] - if (not1.tie_ty) { - k3 = not1.tie_n - not1.tie_n = { s: k2 || k1 } - draw_note_ties(not1, job) - not1.tie_n = k3 - } - } - return - } - - // draw the ties or memorize the ones without ending - for (i = 0; i <= nh1; i++) { - not1 = k1.notes[i] - if (!not1.tie_ty) - continue - if (!not1.s) - not1.s = k1 - if (not1.tie_n) - draw_note_ties(not1, job) - else - mhead3.push(not1) /* no match */ - } - - /* if any bad tie, try an other voice of the same staff */ - if (!mhead3.length) - return /* no bad tie */ - - time = k1.time + k1.dur - k3 = k1.ts_next - if (job != 1) - job = 0 - while (k3 && k3.time < time) - k3 = k3.ts_next - while (k3 && k3.time == time) { - if (k3.type != C.NOTE - || k3.st != k1.st) { - k3 = k3.ts_next - continue - } - for (i = mhead3.length; --i >= 0; ) { - not1 = mhead3[i] - pit = not1.opit || not1.pit - for (j = k3.nhd; j >= 0; j--) { - not3 = k3.notes[j] - pit2 = not3.opit || not3.pit - if (pit2 == pit) { - not1.tie_n = not3 - if (!not3.s) - not3.s = k3 - draw_note_ties(not1, job) - mhead3.splice(i, 1) - break - } - } - } - if (!mhead3.length) - break - k3 = k3.ts_next - } - - if (mhead3.length) - error(1, k1, "Bad tie") -} +} // draw_tie() /* -- draw all ties between neighboring notes -- */ function draw_all_ties(p_voice) { - var s, s1, s2, clef_chg, time, x, dx, s_next, m - - function draw_ties_g(s1, s2, job) { - if (s1.type == C.GRACE) { - for (var g = s1.extra; g; g = g.next) { - if (g.tie_s) - draw_ties(g, s2, job) - } - } else { - draw_ties(s1, s2, job) - } - } // draw_ties_g() + var s, s1, s2, clef_chg, x, dx, m, not1, not2, + tim2 = 0 /* search the start of ties */ // clef_chg = false - s_next = p_voice.sym - set_color(s_next.color) - while (1) { - for (s1 = s_next; s1; s1 = s1.next) { - if (s1.ti2 // if no tie start - && (s1 != s_next - || !s_next.prev)) { // (at start of line) - s = s1.ti2 - s.x = s1.x - s2 = s.next - s.next = s1 - s.st = s1.st - s.time = s1.time - s.dur - draw_ties(s, s1, 1) - s.next = s2 // restore the linkage for play - } - if (s1.tie_s) - break - } - if (!s1) - break - - // check if the tied note is in this line - s2 = s1.tie_s // ending note of the tie - if (s2.v == s1.v) { - s_next = s2 - s = s1 - while (1) { - if (!s.next) { - s2 = s - s = null - break - } - s = s.next - if (s == s2) - break - } - } else { - s_next = s1.next - s = s1 - while (1) { - if (!s.ts_next) { - s = null - break - } - s = s.ts_next - if (s == s2) - break - } - if (!s) { - s2 = s1 - while (s2.next) - s2 = s2.next - } - } - - if (!s) { // end of line - draw_ties_g(s1, s2, 2); - break - } - - // check if some clef changes (may occur in an other voice) - time = s1.time + s.dur + s1 = p_voice.sym + set_color(s1.color) + for ( ; s1; s1 = s1.next) { + if (s1.ti2 // if end of tie + && s1.time != tim2) { // and new end + for (m = 0; m <= s1.nhd; m++) { + not2 = s1.notes[m] + not1 = not2.tie_s + if (!not1 + || not1.s.v != s1.v) // (already done) + continue + draw_tie(not1, not2, 1) + } + } + if (!s1.ti1) // if not start of tie + continue + + // get the end of the tie(s) + if (s1.type == C.GRACE) { + for (s = s1.extra; s; s = s.next) { + for (m = 0; m <= s1.nhd; m++) { + not1 = s.notes[m] + not2 = not1.tie_e + if (!not2) + continue + draw_tie(not1, not2) + tim2 = not2.s.time + } + } + continue + } + + for (m = 0; m <= s1.nhd; m++) { + not1 = s1.notes[m] + not2 = not1.tie_e + if (!not2) { + if (not1.tie_ty) + draw_tie(not1, not1, 2) + continue + } + s2 = not2.s + if (tsnext && s2.time >= tsnext.time) { // if end in next line + draw_tie(not1, not2, 2) + continue + } + tim2 = s2.time + + // check if some clef changes (can occur in an other voice) for (s = s1.ts_next; s != s2; s = s.ts_next) { if (s.st != s1.st) continue - if (s.time > time) - break if (s.type == C.CLEF) { clef_chg = true break } } /* ties with clef or staff change */ if (clef_chg || s1.st != s2.st) { - clef_chg = false; - dx = (s2.x - s1.x) * .4; - x = s2.x; - s2.x -= dx - if (s2.x > s1.x + 32.) - s2.x = s1.x + 32.; - draw_ties_g(s1, s2, 2); - s2.x = x; - x = s1.x; - s1.x += dx - if (s1.x < s2.x - 24.) - s1.x = s2.x - 24.; - draw_ties(s1, s2, 3); - s1.x = x - continue - } - draw_ties_g(s1, s2, s2.type == C.NOTE ? 0 : 2) + draw_tie(not1, not2, 2) + draw_tie(not1, not2, 3) + clef_chg = false + } else { + draw_tie(not1, not2) + } + } } } /* -- draw the symbols near the notes -- */ /* (the staves are not yet defined) */ @@ -2825,18 +2706,19 @@ * - decorations near the notes * - measure bar numbers * - decorations tied to the notes * - tuplets and slurs * - not scaled - * - chord symbols - * - staff decorations + * - measure numbers * - lyrics - * - measure numbers + * - staff decorations + * - chord symbols + * - repeat brackets * The buffer output is delayed until the definition of the staff system */ function draw_sym_near() { - var p_voice, p_st, s, v, st, y, g, w, i, st, dx, top, bot, ymn, + var p_voice, p_st, s, v, st, y, g, w, i, st, dx, top, bot, ymn, slur, output_sav = output; // set the staff offsets of a beam function set_yab(s1, s2) { var y, @@ -2864,18 +2746,17 @@ } } } // set_yab() output = "" + YSTEP = Math.ceil(realwidth / 2) // initialize the min/max vertical offsets in the staves for (st = 0; st <= nstaff; st++) { p_st = staff_tb[st] - if (!p_st.top) { - p_st.top = new Float32Array(YSTEP) - p_st.bot = new Float32Array(YSTEP) - } + p_st.top = new Float32Array(YSTEP) + p_st.bot = new Float32Array(YSTEP) for (i = 0; i < YSTEP; i++) { p_st.top[i] = 0 p_st.bot[i] = 24 } // p_st.top.fill(0.) @@ -2889,18 +2770,33 @@ p_voice = voice_tb[v] for (s = p_voice.sym; s; s = s.next) { switch (s.type) { case C.GRACE: + slur = 0 for (g = s.extra; g; g = g.next) { if (g.beam_st && !g.beam_end) { self.calculate_beam(bm, g) if (bm.s2) set_yab(g, bm.s2) } + if (g.sls || g.sl1) + slur++ } + if (!s.p_v.ckey.k_bagpipe // no slur when bagpipe + && s.fmt.graceslurs + && !s.gr_shift // tied to previous note + && !slur // explicit slur + && !s.ti1 // some tie + && s.next + && s.next.type == C.NOTE) + grace_slur(s) break + } + } + for (s = p_voice.sym; s; s = s.next) { + switch (s.type) { case C.NOTE: if ((s.beam_st && !s.beam_end) || (first_note && !s.beam_st)) { first_note = false; self.calculate_beam(bm, s) @@ -3034,11 +2930,10 @@ } } if (cfmt.measurenb >= 0) draw_measnb(); - draw_deco_staff(); /* if any lyric, draw them now as unscaled */ set_dscale(-1) // set_sscale(-1) for (v = 0; v < voice_tb.length; v++) { @@ -3046,10 +2941,12 @@ if (p_voice.have_ly) { draw_all_lyrics() break } } + + draw_deco_staff() set_dscale(-1); output = output_sav } @@ -3114,10 +3011,11 @@ * staff_tb[st].staffscale + h2 * (a_p.length - 2) // if instrument with 2 staves, center the voice name if ((cur_sy.staves[st].flags & OPEN_BRACE) + && st + 1 < staff_tb.length && (cur_sy.staves[st + 1].flags & CLOSE_BRACE) && !staff_d[st + 1]) y -= (staff_tb[st].y - staff_tb[st + 1].y) * .5 for (n = 0; n < a_p.length; n++) { p = a_p[n]; @@ -3127,12 +3025,13 @@ } } // -- set the y offset of the staves and return the height of the whole system -- function set_staff() { -var s, i, st, prev_staff, v, + var i, st, prev_staff, v, y, staffsep, dy, maxsep, mbot, val, p_voice, p_staff, + fmt = tsfirst.fmt, sy = cur_sy /* set the scale of the voices */ for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] @@ -3168,11 +3067,11 @@ return y } /* set the vertical offset of the 1st staff */ y *= p_staff.staffscale; - staffsep = cfmt.staffsep * .5 + + staffsep = fmt.staffsep * .5 + p_staff.topbar * p_staff.staffscale if (y < staffsep) y = staffsep if (y < p_staff.ann_top) // absolute annotation y = p_staff.ann_top; @@ -3252,14 +3151,14 @@ } if (st < 0) /* no symbol in this system ! */ return y } dy = -mbot; - staffsep = cfmt.staffsep * .5 + staffsep = fmt.staffsep * .5 if (dy < staffsep) dy = staffsep; - maxsep = cfmt.maxstaffsep * .5 + maxsep = fmt.maxstaffsep * .5 if (dy > maxsep) dy = maxsep; // return the height of the whole staff system return y + dy @@ -3272,12 +3171,12 @@ xstaff = [], stl = [], // all staves in the line bar_bot = [], bar_height = [], ba = [], // bars [symbol, bottom, height] - sb = [], - thb = [] + sb = "", + thb = "" /* -- set the bottom and height of the measure bars -- */ function bar_set() { var st, staffscale, top, bot, dy = 0 @@ -3379,10 +3278,11 @@ && s.ts_prev.type != C.BAR) h = p_staff.topbar * p_staff.staffscale; s.ymx = s.ymn + h; + set_sscale(-1) anno_start(s) if (s.color) set_color(s.color); // compute the middle vertical offset of the staff @@ -3400,29 +3300,31 @@ } else { xygl(x, yb, "mrep2") if (s.v == cur_sy.top_voice) nrep_out(x, yb + p_staff.topbar, s.bar_mrep) } + set_sscale(-1) } if (bar_type == '||:') bar_type = '[|:' for (i = bar_type.length; --i >= 0; ) { switch (bar_type[i]) { case "|": if (s.bar_dotted) { - set_sscale(-1); w = (5 * p_staff.staffscale).toFixed(1); out_XYAB( '\n', x, bot, w, h) } else if (s.color) { out_XYAB('\n', x, bot, h) } else { - sb.push(new Float32Array([x, bot, h])) + sb += 'M' + sx(x).toFixed(1) + + ' ' + self.sy(bot).toFixed(1) + + 'v-' + h.toFixed(1) } break default: // case "[": // case "]": @@ -3429,16 +3331,19 @@ x -= 3; if (s.color) out_XYAB('\n', x + 1.5, bot, h) else - thb.push(new Float32Array([x + 1.5, bot, h])) + thb += 'M' + sx(x + 1.5).toFixed(1) + + ' ' + self.sy(bot).toFixed(1) + + 'v-' + h.toFixed(1) break case ":": x -= 2; set_sscale(st); xygl(x + 1, yb - 12, "rdots") + set_sscale(-1) break } x -= 3 } set_color(); @@ -3460,30 +3365,67 @@ } if (bx) gene.curfont.box = bx set_sscale(-1) - l = sb.length - if (l) { // single bars [x, y, h] + if (sb) // single bars output += '\n' - } - - l = thb.length - if (l) { // thick bars [x, y, h] + + sb + + '"/>\n' + + if (thb) // thick bars [x, y, h] output += '\n' - } + + thb + + '"/>\n' } // out_bars() + + // set the helper lines of rests + function hl_rest(s) { + var j, + p_st = staff_tb[s.st], + i = 5 - s.nflags, // rest_tb index (5 = C_XFLAGS) + x = s.x, + y = s.y + + if (i < 6) // no ledger line if rest smaller than minim + return + + if (i == 7 && y == 12 + && p_st.stafflines.length <= 2) + y -= 6 // semibreve a bit lower + + j = y / 6 + switch (i) { + default: + switch (p_st.stafflines[j + 1]) { + case '|': + case '[': + break + default: + set_hl(p_st, j + 1, x, -7, 7) + break + } + if (i == 9) { // longa + y -= 6 + j-- + } + break + case 7: // semibreve + y += 6 + j++ + case 6: // minim + break + } + switch (p_st.stafflines[j]) { + case '|': + case '[': + break + default: + set_hl(p_st, j, x, -7, 7) + break + } + } // hl_rest() // ---- draw_systems() ---- /* draw the staff, skipping the staff breaks */ for (st = 0; st <= nstaff; st++) { @@ -3549,11 +3491,17 @@ } cur_sy = sy; bar_set() continue case C.BAR: // display the bars after the staves - if (s.second || s.invis || !s.bar_type) + if (s.invis || !s.bar_type + || !cur_sy.st_print[s.st]) + break + if (s.second + && (!s.ts_prev + || (s.ts_prev.type == C.BAR + && s.ts_prev.st == s.st))) break ba.push([s, bar_bot[s.st], bar_height[s.st]]) break case C.STBRK: if (cur_sy.voices[s.v] @@ -3589,10 +3537,25 @@ break draw_staff(st, x, x2) xstaff[st] = s.x } break + case C.GRACE: + for (s2 = s.extra; s2; s2 = s2.next) + self.draw_hl(s2) + break + case C.NOTE: + if (!s.invis) + self.draw_hl(s) + break + case C.REST: + if (s.fmr // if full measure rest + || (s.rep_nb && s.rep_nb >= 0)) + center_rest(s) + if (!s.invis) + hl_rest(s) + break // default: //fixme:does not work for "%%staves K: M: $" */ //removed for K:/M: in empty staves // if (!cur_sy.st_print[st]) // s.invis = true @@ -3607,10 +3570,13 @@ x = xstaff[st] if (x < 0 || x >= realwidth) continue draw_staff(st, x, realwidth) } + + // the ledger lines + draw_all_hl() // and the bars out_bars() draw_vname(indent, stl) @@ -3620,11 +3586,11 @@ /* -- draw remaining symbols when the staves are defined -- */ // (possible hook) Abc.prototype.draw_symbols = function(p_voice) { var bm = {}, - s, g, x, y, st; + s, x, y, st; // bm.s2 = undefined for (s = p_voice.sym; s; s = s.next) { if (s.invis) { switch (s.type) { @@ -3687,11 +3653,13 @@ } else { y += s.ymn + 6 if (s.clef_small) y += 1 } - xygl(x - 2, y, "oct") + xygl(x - 2, y, (s.clef_octave == 7 + || s.clef_octave == -7) + ? "oct" : "oct2") } anno_a.push(s) break case C.METER: p_voice.meter = s @@ -3714,18 +3682,11 @@ anno_start(s); self.draw_keysig(x, s); anno_a.push(s) break case C.MREST: - set_scale(s); - x += 32; - anno_start(s); - xygl(x, staff_tb[s.st].y + 12, "mrest"); - out_XYAB('A\n', - x, staff_tb[s.st].y + 28, s.nmes); - anno_a.push(s) + draw_mrest(s) break case C.GRACE: set_scale(s); draw_gracenotes(s) break @@ -3735,11 +3696,10 @@ case C.CUSTOS: set_scale(s); draw_note(s, 0) break case C.BLOCK: // no width - case C.PART: case C.REMARK: case C.STAVES: case C.TEMPO: break default: @@ -3753,55 +3713,10 @@ /* -- draw all symbols -- */ function draw_all_sym() { var p_voice, v, n = voice_tb.length - // draw all the helper/ledger lines - function draw_all_hl() { - var st, p_st - - function hlud(hla, d) { - var hl, hll, i, xp, dx2, x2, - n = hla.length - - if (!n) - return - for (i = 0; i < n; i++) { // for all lines - hll = hla[i] - if (!hll || !hll.length) - continue - xp = sx(hll[0][0]) // previous x - output += - '\n' - } - } // hlud() - - for (st = 0; st <= nstaff; st++) { - p_st = staff_tb[st] - if (!p_st.hlu) - continue // (staff not yet displayed) - set_sscale(st) - hlud(p_st.hlu, 6) - hlud(p_st.hld, -6) - } - } // draw_all_hl() - for (v = 0; v < n; v++) { p_voice = voice_tb[v] if (p_voice.sym && p_voice.sym.x != undefined) { self.draw_symbols(p_voice) @@ -3810,27 +3725,46 @@ set_color() } } self.draw_all_deco() - draw_all_hl() glout() // output the symbols anno_put() // before outputting the symbol annotations set_sscale(-1) /* restore the scale */ } /* -- set the tie directions for one voice -- */ function set_tie_dir(s) { - var i, ntie, dir, sec, pit, ty + var i, ntie, dir, sec, pit, ty, s2 for ( ; s; s = s.next) { - if (!s.tie_s) + if (!s.ti1) continue + + sec = ntie = 0; + pit = 128 + for (i = 0; i <= s.nhd; i++) { + if (s.notes[i].tie_ty) { + ntie++ + if (pit < 128 + && s.notes[i].pit <= pit + 1) + sec++; + pit = s.notes[i].pit + s2 = s.notes[i].tie_e + } + } - /* if other voice, set the ties in opposite direction */ - if (s.multi != 0) { + if (s2 && s.stem * s2.stem < 0) + dir = pit >= 22 // up if above middle staff + ? C.SL_ABOVE : C.SL_BELOW + else if (s.multi) dir = s.multi > 0 ? C.SL_ABOVE : C.SL_BELOW + else + dir = s.stem < 0 ? C.SL_ABOVE : C.SL_BELOW + + // if other voice, set the ties in opposite direction + if (s.multi) { for (i = 0; i <= s.nhd; i++) { ty = s.notes[i].tie_ty if (!((ty & 0x07) == C.SL_AUTO)) continue s.notes[i].tie_ty = (ty & C.SL_DOTTED) | dir @@ -3837,23 +3771,11 @@ } continue } /* if one note, set the direction according to the stem */ - sec = ntie = 0; - pit = 128 - for (i = 0; i <= s.nhd; i++) { - if (s.notes[i].tie_ty) { - ntie++ - if (pit < 128 - && s.notes[i].pit <= pit + 1) - sec++; - pit = s.notes[i].pit - } - } if (ntie <= 1) { - dir = s.stem < 0 ? C.SL_ABOVE : C.SL_BELOW for (i = 0; i <= s.nhd; i++) { ty = s.notes[i].tie_ty if (ty) { if ((ty & 0x07) == C.SL_AUTO) s.notes[i].tie_ty = @@ -3861,11 +3783,11 @@ break } } continue } - if (sec == 0) { + if (!sec) { if (ntie & 1) { /* in chords with an odd number of notes, the outer noteheads are paired off * center notes are tied according to their position in relation to the * center line */ ntie = (ntie - 1) / 2; @@ -3948,11 +3870,11 @@ s = s.next if (!s) continue set_tie_dir(s) for ( ; s; s = s.next) { - if (!s.tie_s) + if (!s.ti1) continue if (s.notes[0].pit < 20 && s.notes[0].tie_ty && (s.notes[0].tie_ty & 0x07) == C.SL_BELOW) ; Index: core/format.js ================================================================== --- core/format.js +++ core/format.js @@ -25,36 +25,48 @@ 'sans-serif': 1, 'sans-serifBold': 1, Palatino: 1.1, monospace: 1.35 }, + txt_ff = "text,serif", // text font-family (serif for compatibility) fmt_lock = {} var cfmt = { - annotationfont: {name: "sans-serif", size: 12}, + "abc-version": "1", // default: old version + annotationfont: {name: "text,sans-serif", size: 12}, aligncomposer: 1, beamslope: .4, // max slope of a beam // botmargin: .7 * IN, // != 1.8 * CM, + bardef: { + "[": "", // invisible + "[]": "", + "|:": "[|:", + "|::": "[|::", + "|:::": "[|:::", + ":|": ":|]", + "::|": "::|]", + ":::|": ":::|]", + "::": ":][:" + }, breaklimit: .7, breakoneoln: true, cancelkey: true, - composerfont: { name: "serif", style: "italic", size: 14 }, + composerfont: { name: txt_ff, style: "italic", size: 14 }, composerspace: 6, // contbarnb: false, - dblrepbar: ':][:', decoerr: true, dynalign: true, - footerfont: { name: "serif", size: 16 }, + footerfont: { name: txt_ff, size: 16 }, fullsvg: '', - gchordfont: { name: "sans-serif", size: 12 }, - gracespace: new Float32Array([4, 8, 11]), // left, inside, right + gchordfont: { name: "text,sans-serif", size: 12 }, + gracespace: new Float32Array([6, 8, 11]), // left, inside, right graceslurs: true, - headerfont: { name: "serif", size: 16 }, - historyfont: { name: "serif", size: 16 }, + headerfont: { name: txt_ff, size: 16 }, + historyfont: { name: txt_ff, size: 16 }, hyphencont: true, indent: 0, - infofont: {name: "serif", style: "italic", size: 14 }, + infofont: {name: txt_ff, style: "italic", size: 14 }, infoname: 'R "Rhythm: "\n\ B "Book: "\n\ S "Source: "\n\ D "Discography: "\n\ N "Notes: "\n\ @@ -67,57 +79,91 @@ linewarn: true, maxshrink: .65, // nice scores maxstaffsep: 2000, maxsysstaffsep: 2000, measrepnb: 1, - measurefont: {name: "serif", style: "italic", size: 10}, + measurefont: {name: txt_ff, style: "italic", size: 10}, measurenb: -1, musicfont: {name: "music", src: musicfont, size: 24}, musicspace: 6, -// notespacingfactor: 1.33 - partsfont: {name: "serif", size: 15}, +// notespacingfactor: "1.3, 38", + partsfont: {name: txt_ff, size: 15}, parskipfac: .4, partsspace: 8, // pageheight: 29.7 * CM, pagewidth: 21 * CM, printmargin: 0, rightmargin: 1.4 * CM, - rbdbstop: true, rbmax: 4, rbmin: 2, - repeatfont: {name: "serif", size: 13}, + repeatfont: {name: txt_ff, size: 13}, scale: 1, slurheight: 1.0, + spatab: // spacing table (see "notespacingfactor" and set_space()) + new Float32Array([ // default = "1.3, 38" + 10.2, 13.3, 17.3, 22.48, 29.2, + 38, + 49.4, 64.2, 83.5, 108.5]), staffsep: 46, stemheight: 21, // one octave stretchlast: .25, stretchstaff: true, - subtitlefont: {name: "serif", size: 16}, + subtitlefont: {name: txt_ff, size: 16}, subtitlespace: 3, sysstaffsep: 34, - tempofont: {name: "serif", weight: "bold", size: 12}, - textfont: {name: "serif", size: 16}, + tempofont: {name: txt_ff, weight: "bold", size: 12}, + textfont: {name: txt_ff, size: 16}, // textoption: undefined, textspace: 14, tieheight: 1.0, - titlefont: {name: "serif", size: 20}, + titlefont: {name: txt_ff, size: 20}, // titleleft: false, titlespace: 6, titletrim: true, // transp: 0, // global transpose // topmargin: .7 * IN, topspace: 22, tuplets: [0, 0, 0, 0], - tupletfont: {name: "serif", style: "italic", size: 12}, - vocalfont: {name: "serif", weight: "bold", size: 13}, + tupletfont: {name: txt_ff, style: "italic", size: 12}, + vocalfont: {name: txt_ff, weight: "bold", size: 13}, vocalspace: 10, - voicefont: {name: "serif", weight: "bold", size: 13}, + voicefont: {name: txt_ff, weight: "bold", size: 13}, // voicescale: 1, writefields: "CMOPQsTWw", - wordsfont: {name: "serif", size: 16}, + wordsfont: {name: txt_ff, size: 16}, wordsspace: 5 } + +// parameters that are used in the symbols +var sfmt = { +bardef: true, +barsperstaff: true, +beamslope: true, +breaklimit: true, +bstemdown: true, +cancelkey: true, +dynalign: true, +flatbeams: true, +gracespace: true, +hyphencont: true, +keywarn: true, +maxshrink: true, +maxstaffsep: true, +measrepnb: true, +rbmax: true, +rbmin: true, +shiftunison: true, +slurheight: true, +squarebreve: true, +staffsep: true, +stemheight: true, +stretchlast: true, +stretchstaff: true, +tieheight: true, +timewarn: true, +vocalspace: true +} // sfmt function get_bool(param) { return !param || !/^(0|n|f)/i.test(param) // accept void as true ! } @@ -126,11 +172,11 @@ var i, font, a = info_split(param) // a[0] = font name if (a.length <= 1) return - var scale = parseFloat(a[a.length - 1]) + var scale = +a[a.length - 1] if (isNaN(scale) || scale <= 0.5) { syntax(1, "Bad scale value in %%font") return } @@ -167,11 +213,11 @@ } else { font = { name: font.name, size: font.size, box: font.box, - pad: font.pad + pad: font.pad || 0 } } cfmt[xxxfont] = font; // fill the values @@ -186,11 +232,11 @@ } p = p.replace(a[0], a[2]) } a = p.match(/\s+padding=([\d.]+)(\s|$)/) if (a) { // if padding - font.pad = a[1] ? Number(a[1]) : 0 + font.pad = a[1] ? +a[1] : 0 p = p.replace(a[0], a[2]) } a = p.match(/\s+class=(.*?)(\s|$)/) if (a) { @@ -219,65 +265,97 @@ // the font size is the last item a = p.match(/\s+([0-9.]+|\*)$/) if (a) { if (a[1] != "*") - font.size = Number(a[1]) + font.size = +a[1] p = p.replace(a[0], "") } - a = p.match(/[- ]?[nN]ormal/) - if (a) { + // accept url(...) as the font name + if (p[0] == 'u' && p.slice(0, 4) == "url(") { + n = p.indexOf(')', 1) + if (n < 0) { + syntax(1, "No end of url in font family") + return + } + p = p.slice(0, n + 1) + + font.src = p + font.fid = font_tb.length + font_tb.push(font) font.normal = true - p = p.replace(a[0], '') - } - - a = p.match(/[- ]?[bB]old/) - if (a) { - font.weight = "bold" - p = p.replace(a[0], '') - } - a = p.match(/[- ]?[iI]talic/) - if (a) { - font.style = "italic" - p = p.replace(a[0], '') - } - a = p.match(/[- ]?[oO]blique/) - if (a) { - font.style = "oblique" - p = p.replace(a[0], '') + 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) + } + + // extract the font attributes + a = p.match(/[- ]?[nN]ormal/) + if (a) { + font.normal = true + p = p.replace(a[0], '') + } + + // font weight + a = p.match(abc2svg.ft_re) + if (a) { + font.weight = abc2svg.ft_w[a[0].replace(/[ -]/, '') + .toLowerCase()] + p = p.replace(a[0], '') + } + + a = p.match(/[- ]?[iI]talic/) + if (a) { + font.style = "italic" + p = p.replace(a[0], '') + } + a = p.match(/[- ]?[oO]blique/) + if (a) { + font.style = "oblique" + p = p.replace(a[0], '') + } + + // here is the font family + p = p.trim() + + switch (p) { + case "": + case "*": return + 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 + default: +//hack: the font "Figurato" is used for figured bass + if (p.indexOf("Fig") > 0) + font.figb = true + break + } } if (font.size) set_font_fac(font) else font.swfac = 0 - switch (p) { - case "": - case "*": return - case "Times-Roman": - case "Times": p = "serif"; break - case "Helvetica": p = "sans-serif"; break - case "Courier": p = "monospace"; break - } - - // accept url(...) as the font name - if (p[3] == '(') { - font.src = p - font.fid = font_tb.length - font_tb.push(font) - p = 'ft' + font.fid - } + font.name = p } // get a length with a unit - return the number of pixels function get_unit(param) { var v = param.toLowerCase().match(/([\d.]+)(.*)/) if (!v) return NaN - v[1] = Number(v[1]) + v[1] = +v[1] switch (v[2]) { case "cm": return v[1] * CM case "in": return v[1] * IN @@ -314,16 +392,20 @@ var textopt = { align: 'j', center: 'c', fill: 'f', justify: 'j', + obeylines: 'l', ragged: 'f', right: 'r', skip: 's' } -function get_textopt(param) { - return textopt[param] +function get_textopt(v) { + var i = v.indexOf(' ') + if (i > 0) + v = v.slice(0, i) + return textopt[v] } /* -- position of a voice element -- */ var posval = { above: C.SL_ABOVE, @@ -339,11 +421,11 @@ /* -- set the position of elements in a voice -- */ function set_pos(k, v) { // keyword, value k = k.slice(0, 3) if (k == "ste") k = "stm" - set_v_param("pos", k + ' ' + v) + set_v_param("pos", '"' + k + ' ' + v + '"') } // set/unset the fields to write function set_writefields(parm) { var c, i, @@ -405,10 +487,14 @@ else param_set_font(cmd, param) return } + // duplicate the global parameters if already used by symbols + if (sfmt[cmd] && parse.ufmt) + cfmt = Object.create(cfmt) + switch (cmd) { case "aligncomposer": case "barsperstaff": case "infoline": case "measurenb": @@ -421,13 +507,13 @@ syntax(1, "Bad integer value"); break } cfmt[cmd] = v break + case "abc-version": case "bgcolor": case "fgcolor": - case "dblrepbar": case "titleformat": cfmt[cmd] = param break case "beamslope": case "breaklimit": // float values @@ -436,13 +522,12 @@ case "pagescale": case "parskipfac": case "scale": case "slurheight": case "stemheight": - case "stretchlast": case "tieheight": - f = parseFloat(param) + f = +param if (isNaN(f)) { syntax(1, errs.bad_val, '%%' + cmd) break } switch (cmd) { @@ -460,35 +545,47 @@ case "measurebox": case "partsbox": param_set_font(cmd.replace("box", "font"), // font "* * " + (get_bool(param) ? "box" : "nobox")) break + case "altchord": case "bstemdown": case "breakoneoln": case "cancelkey": case "checkbars": case "contbarnb": case "custos": case "decoerr": - case "dynalign": case "flatbeams": case "graceslurs": case "graceword": case "hyphencont": case "keywarn": case "linewarn": case "quiet": - case "rbdbstop": case "singleline": case "squarebreve": case "splittune": case "straightflags": case "stretchstaff": case "timewarn": case "titlecaps": case "titleleft": cfmt[cmd] = get_bool(param) + break + case "dblrepbar": + param = ":: " + param + // fall thru + case "bardef": // %%bardef oldbar newbar + v = param.split(/\s+/) + if (v.length != 2) { + syntax(1, errs.bad_val, "%%bardef") + } else { + if (parse.ufmt) + cfmt.bardef = Object.create(cfmt.bardef) // new object + cfmt.bardef[v[0]] = v[1] + } break case "chordalias": v = param.split(/\s+/) if (!v.length) syntax(1, errs.bad_val, "%%chordalias") @@ -566,16 +663,18 @@ cfmt[cmd] = param break case "gracespace": v = param.split(/\s+/) for (i = 0; i < 3; i++) - if (isNaN(Number(v[i]))) { + if (isNaN(+v[i])) { syntax(1, errs.bad_val, "%%gracespace") break } + if (parse.ufmt) + cfmt[cmd] = new Float32Array(3) for (i = 0; i < 3; i++) - cfmt[cmd][i] = Number(v[i]) + cfmt[cmd][i] = +v[i] break case "tuplets": cfmt[cmd] = param.split(/\s+/); v = cfmt[cmd][3] if (v // if 'where' @@ -584,34 +683,54 @@ break case "infoname": set_infoname(param) break case "notespacingfactor": - f = parseFloat(param) - if (isNaN(f) || f < 1 || f > 2) { + v = param.match(/([.\d]+)[,\s]*(\d+)?/) + if (v) { + f = +v[1] + if (isNaN(f) || f < 1 || f > 2) { + f = 0 + } else if (v[2]) { + f2 = +v[2] + if (isNaN(f)) + f = 0 + } else { + f2 = cfmt.spatab[5] + } + } + if (!f) { syntax(1, errs.bad_val, "%%" + cmd) break } + cfmt[cmd] = param // (for dump) + + // in the table 'spatab', + // the width of notes is indexed by log2(note_length) + cfmt.spatab = new Float32Array(10) i = 5; // index of crotchet - f2 = space_tb[i] - for ( ; --i >= 0; ) { - f2 /= f; - space_tb[i] = f2 - } + do { + cfmt.spatab[i] = f2 + f2 /= f + } while (--i >= 0) i = 5; - f2 = space_tb[i] - for ( ; ++i < space_tb.length; ) { + f2 = cfmt.spatab[i] + for ( ; ++i < cfmt.spatab.length; ) { f2 *= f; - space_tb[i] = f2 + cfmt.spatab[i] = f2 } break case "play": cfmt.sound = "play" // without clef break case "pos": - cmd = param.split(/\s+/); - set_pos(cmd[0], cmd[1]) + cmd = param.match(/(\w*)\s+(.*)/) + if (!cmd || !cmd[2]) { + syntax(1, "Error in %%pos") + break + } + set_pos(cmd[1], cmd[2]) break case "sounding-score": if (cfmt.sound != "play") cfmt.sound = "sounding" break @@ -634,16 +753,23 @@ img.chg = true break case "textoption": cfmt[cmd] = get_textopt(param) break + case "dynalign": + case "stretchlast": case "titletrim": - v = Number(param) + v = +param if (isNaN(v)) - cfmt[cmd] = get_bool(param) - else - cfmt[cmd] = v + v = get_bool(param) ? 0 : 1 + if (cmd[0] == 's') { // stretchlast + if (v < 0 || v > 1) { + syntax(1, errs.bad_val, '%%' + cmd) + break + } + } + cfmt[cmd] = v break case "combinevoices": syntax(1, "%%combinevoices is deprecated - use %%voicecombine instead") break case "voicemap": @@ -650,15 +776,29 @@ set_v_param("map", param) break case "voicescale": set_v_param("scale", param) break + // deprecated + case "rbdbstop": + v = get_bool(param) + if (v && cfmt["abc-version"] >= "2.2") + cfmt["abc-version"] = "1" + else if (!v && cfmt["abc-version"] < "2.2") + cfmt["abc-version"] = "2.2" + break default: // memorize all global commands - if (parse.state == 0) // (needed for modules) + if (!parse.state) // (needed for modules) cfmt[cmd] = param break } + + // check if already a same format + if (sfmt[cmd] && parse.ufmt) { + // to do... + parse.ufmt = false + } } // font stuff // build a font style @@ -700,16 +840,20 @@ set_font_fac(font) if (!font.pad) font.pad = 0 } add_fstyle(".f" + font.fid + - (typeof document == "undefined" ? cfmt.fullsvg : "") + + (cfmt.fullsvg || "") + "{" + style_font(font) + "}") if (font.src) add_fstyle("@font-face{\n\ font-family:" + font.name + ";\n\ src:" + font.src + "}") + if (font == cfmt.musicfont) // add more music font style + add_fstyle(".f" + font.fid + + (cfmt.fullsvg || "") + + ' text,tspan{fill:currentColor;white-space:pre}') } } // get the font of the 'xxxfont' parameter function get_font(fn) { @@ -721,11 +865,11 @@ syntax(1, "Unknown font $1", '$' + fn[1]); return gene.curfont } if (!font.name || !font.size) { // if incomplete user font - font2 = Object.create(gene.curfont) + font2 = Object.create(gene.deffont) if (font.name) font2.name = font.name if (font.normal) { if (font2.weight) // !! don't use delete !! font2.weight = null @@ -735,12 +879,12 @@ if (font.weight) font2.weight = font.weight if (font.style) font2.style = font.style } -// if (font.class) -// font2.class = font.class + if (font.src) + font2.src = font.src if (font.size) font2.size = font.size st = st_font(font2) if (font.class) { font2.class = font.class Index: core/front.js ================================================================== --- core/front.js +++ core/front.js @@ -1,8 +1,8 @@ // abc2svg - front.js - ABC parsing front-end // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2021 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 @@ -102,11 +102,11 @@ } dst += c j = i + 5 continue case 't': // TAB - dst += ' '; + dst += '\t'; j = i + 1 continue case 'n': // new line (voice name) dst += '\n'; j = i + 1 @@ -119,10 +119,12 @@ continue } // try unicode combine characters c2 = src[i + 1] + if (!c2) + break // !! the next test is true if c2 is undefined !! if (!/[A-Za-z]/.test(c2)) break switch (c) { case '`': dst += c2 + "\u0300" // grave @@ -181,12 +183,12 @@ j = i + 2 continue } break } - if (flag == 'w') - dst += '\\' + if (flag == 'w') // if lyrics line (w:) + dst += '\\' // keep the backslash dst += c j = i + 1 } return dst + src.slice(j) } @@ -284,10 +286,12 @@ maps = sav.maps; mac = sav.mac; maci = sav.maci; parse.tune_v_opts = null; parse.scores = null; + parse.ufmt = false + delete parse.part init_tune() img.chg = true; set_page(); } // end_tune() @@ -395,10 +399,12 @@ // scan the file if (bol == undefined) bol = 0 if (!eof) eof = file.length + if (file.slice(bol, bol + 5) == "%abc-") + cfmt["abc-version"] = /[1-9.]+/.exec(file.slice(bol + 5, bol + 10)) for ( ; bol < eof; bol = parse.eol + 1) { eol = file.indexOf('\n', bol) // get a line if (eol < 0 || eol > eof) eol = eof; parse.eol = eol @@ -483,13 +489,12 @@ end.slice(1), a[1]); parse.eol = eof continue } self.do_begin_end(b, uncomment(a[2]), - file.slice(eol + 1, i).replace( - new RegExp('^' + line0 + line1, 'gm'), - '')); + file.slice(eol + 1, i) + .replace(/^%%/gm,'')) parse.eol = file.indexOf('\n', i + 6) if (parse.eol < 0) parse.eol = eof continue } @@ -707,15 +712,10 @@ case 'w': if (parse.state != 3 || cfmt.writefields.indexOf(line0) < 0) break get_lyrics(text, txt_add == ' ') - if (text.slice(-1) == '\\') { // old continuation - txt_add = ' '; - last_info = line0 - continue - } break case '|': // "|:" starts a music line if (parse.state < 2) continue parse.line.buffer = text Index: core/gchord.js ================================================================== --- core/gchord.js +++ core/gchord.js @@ -1,8 +1,8 @@ // abc2svg - gchord.js - chord symbols // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2022 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -45,11 +45,11 @@ } else { i = ++line.index // search the ending double quote while (1) { j = line.buffer.indexOf('"', i) if (j < 0) { - syntax(1, "No end of guitar chord") + syntax(1, "No end of chord symbol/annotation") return } if (line.buffer[j - 1] != '\\') break i = j + 1 @@ -85,28 +85,41 @@ y_abs = get_float() if (c != ' ') i-- } gch.x = x_abs; - gch.y = y_abs - h_ann / 2 + y_abs -= h_ann * .3 // center vertically + gch.y = y_abs break case '^': + gch.pos = C.SL_ABOVE + // fall thru case '_': + if (c == '_') + gch.pos = C.SL_BELOW + // fall thru case '<': case '>': i++; type = c break default: switch (type) { case 'g': gch.font = get_font("gchord") + gch.pos = curvoice.pos.gch || C.SL_ABOVE + break + case '^': + gch.pos = C.SL_ABOVE + break + case '_': + gch.pos = C.SL_BELOW break case '@': gch.x = x_abs; y_abs -= h_ann; - gch.y = y_abs - h_ann / 2 + gch.y = y_abs break } break } gch.type = type @@ -203,11 +216,27 @@ } // parser: add the parsed list of chord symbols and annotations // to the symbol (note, rest or bar) function csan_add(s) { - s.a_gch = a_gch; + var i, gch + + // there cannot be chord symbols on measure bars + if (s.type == C.BAR) { + for (i = 0; i < a_gch.length; i++) { + if (a_gch[i].type == 'g') { + syntax(1, + "There cannot be chord symbols on measure bars") + a_gch.splice(i) + } + } + } + + if (s.a_gch) + s.a_gch = s.a_gch.concat(a_gch) + else + s.a_gch = a_gch a_gch = null if (curvoice.vtransp) gch_transp(s) } // csan_add @@ -217,12 +246,10 @@ Abc.prototype.gch_build = function(s) { /* split the chord symbols / annotations * and initialize their vertical offsets */ var gch, wh, xspc, ix, - y_above = 0, - y_below = 0, y_left = 0, y_right = 0, GCHPRE = .4; // portion of chord before note // change the accidentals in the chord symbols, @@ -229,18 +256,17 @@ // convert the escape sequences in annotations, and // set the offsets for (ix = 0; ix < s.a_gch.length; ix++) { gch = s.a_gch[ix] if (gch.type == 'g') { - gch.text = gch.text.replace(/##|#|=|bb|b| /g, + gch.text = gch.text.replace(/##|#|=|bb|b/g, function(x) { switch (x) { case '##': return "𝄪" case '#': return "\u266f" case '=': return "\u266e" case 'b': return "\u266d" - case ' ': return '  ' } return "𝄫" }); } else { if (gch.type == '@' @@ -259,15 +285,10 @@ wh[1] += gch.font.pad * 2 switch (gch.type) { case '@': break case '^': /* above */ - xspc = wh[0] * GCHPRE - if (xspc > 8) - xspc = 8; - gch.x = -xspc; - break case '_': /* below */ xspc = wh[0] * GCHPRE if (xspc > 8) xspc = 8; gch.x = -xspc; @@ -310,15 +331,23 @@ // -- draw the chord symbols and annotations // (the staves are not yet defined) // (unscaled delayed output) // (possible hook) Abc.prototype.draw_gchord = function(i, s, x, y) { + if (s.invis && s.play) // play sequence: no chord nor annotation + return var y2, an = s.a_gch[i], h = an.text.wh[1], pad = an.font.pad, - w = an.text.wh[0] + pad * 2 + w = an.text.wh[0] + pad * 2, + dy = 0 + + if (an.font.figb) { + h *= 2.4 + dy = an.font.size * 1.3 + } switch (an.type) { case '_': // below y -= h + pad y_set(s.st, 0, x, w, y - pad) @@ -345,10 +374,14 @@ y += (s.type == C.NOTE ? (((s.notes[s.nhd].pit + s.notes[0].pit) >> 1) - 18) * 3 : 12) // fixed offset on rests and bars - h / 2 + if (y > 24) + y_set(s.st, 1, x, w, y + h + pad) + if (y < 0) + y_set(s.st, 0, x, w, y - pad) break default: // chord symbol if (y >= 0) { y += pad y_set(s.st, 1, x, w, y + h + pad) @@ -378,26 +411,22 @@ set_font(an.font) set_dscale(s.st) if (user.anno_start) user.anno_start("annot", an.istart, an.iend, x - 2, y + h + 2, w + 4, h + 4, s) - xy_str(x, y, an.text) + xy_str(x, y + dy, an.text) if (user.anno_stop) user.anno_stop("annot", an.istart, an.iend, 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, - pos = curvoice.pos.gch == C.SL_BELOW ? -1 : 1, n_an = 0, // max number of annotations minmax = new Array(nstaff + 1) - if (curvoice.pos.gch == C.SL_HIDDEN) - pos = 0 - // set a vertical offset to all the chord symbols/annotations function set_an_yu(j) { var an, i, s, x, y, w for (s = san1 ; s; s = s.ts_next) { @@ -406,21 +435,20 @@ continue i = an.length - j - 1 an = an[i] if (!an) continue - if ((an.type == 'g' && pos > 0) - || an.type == '^') { + if (an.pos == C.SL_ABOVE) { x = s.x + an.x w = an.text.wh[0] if (w && x + w > realwidth) x = realwidth - w // let the text in the page y = y_get(s.st, 1, x, w) // y / staff if (an.type == 'g' && y < minmax[s.st].yup) y = minmax[s.st].yup - } else if ((an.type == 'g' && pos <= 0) - || an.type == '_') { + } else if (an.pos == C.SL_BELOW + || an.pos == C.SL_HIDDEN) { continue } else { x = s.x + an.x y = an.y } @@ -435,12 +463,11 @@ an = s.a_gch if (!an) continue an = an[i] if (!an - || !((an.type == 'g' && pos < 0) - || an.type == '_')) + || an.pos != C.SL_BELOW) continue x = s.x + an.x w = an.text.wh[0] if (w && x + w > realwidth) // let the text inside the page x = realwidth - w @@ -469,15 +496,15 @@ n_an = i while (--i >= 0) { if (an[i].type == 'g') { an = an[i] w = an.text.wh[0] - if (pos > 0) { + if (an.pos == C.SL_ABOVE) { y = y_get(s.st, true, s.x, w) if (y > minmax[s.st].yup) minmax[s.st].yup = y - } else if (pos < 0) { + } else if (an.pos == C.SL_BELOW) { y = y_get(s.st, false, s.x, w) 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-2020 Jean-Francois Moine - LGPL3+ +// Copyright (C) 2014-2021 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-2019 Jean-Francois Moine +// Copyright (C) 2014-2021 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 @@ -31,15 +31,14 @@ syntax(1, "+: symbol line without music") return } } else { if (curvoice.sym_restart) { // new music - curvoice.sym_start = s = curvoice.sym_restart; + curvoice.sym_start = curvoice.sym_restart; curvoice.sym_restart = null - } else { - s = curvoice.sym_start } + s = curvoice.sym_start if (!s) s = curvoice.sym if (!s) { syntax(1, "s: without music") return @@ -70,11 +69,11 @@ j = ++i i = p.indexOf(c, j) if (i < 0) { syntax(1, c == '!' ? "No end of decoration" : - "No end of guitar chord"); + "No end of chord symbol/annotation"); i = p.length continue } d = p.slice(j - 1, i + 1) break @@ -93,11 +92,11 @@ syntax(1, errs.bad_char, c) break } /* store the element in the next note */ - while (s && (s.type != C.NOTE || s.grace)) + while (s && s.type != C.NOTE) s = s.next if (!s) { syntax(1, "Too many elements in symbol line") return } @@ -104,11 +103,12 @@ switch (c) { default: // case '*': break case '!': - deco_cnv([d.slice(1, -1)], s, s.prev) + a_dcn.push(d.slice(1, -1)) + deco_cnv(s, s.prev) break case '"': parse_gchord(d) if (a_gch) // if no error csan_add(s) @@ -115,28 +115,34 @@ break } s = s.next; i++ } - curvoice.lyric_cont = s + curvoice.sym_cont = s } /* -- parse a lyric (vocal) line (w:) -- */ function get_lyrics(text, cont) { - var s, word, p, i, j, ly, dfnt + var s, word, p, i, j, ly, dfnt, ln, c, cf if (curvoice.ignore) return - if (curvoice.pos.voc != C.SL_HIDDEN) + if ((curvoice.pos.voc & 0x07) != C.SL_HIDDEN) curvoice.have_ly = true // get the starting symbol of the lyrics if (cont) { // +: s = curvoice.lyric_cont if (!s) { syntax(1, "+: lyric without music") return + } + dfnt = get_font("vocal") + if (gene.deffont != dfnt) { // if vocalfont change + if (gene.curfont == gene.deffont) + gene.curfont = dfnt + gene.deffont = dfnt } } else { set_font("vocal") if (curvoice.lyric_restart) { // new music curvoice.lyric_start = s = curvoice.lyric_restart; @@ -153,18 +159,19 @@ return } } /* scan the lyric line */ - dfnt = gene.curfont // font at start of line p = text; i = 0 + cf = gene.curfont while (1) { while (p[i] == ' ' || p[i] == '\t') i++ if (!p[i]) break + ln = 0 j = parse.istart + i + 2 // start index switch (p[i]) { case '|': while (s && s.type != C.BAR) s = s.next @@ -174,24 +181,18 @@ } s = s.next; i++ continue case '-': - word = "-\n" - break case '_': - word = "_\n" + word = p[i] + ln = 2 // line continuation break case '*': word = "" break default: - if (p[i] == '\\' - && i == p.length - 1) { - curvoice.lyric_cont = s - return - } word = ""; while (1) { if (!p[i]) break switch (p[i]) { @@ -205,16 +206,25 @@ case '~': word += ' '; // ( ) i++ continue case '-': - word += "\n" + ln = 1 // start of line break case '\\': - word += p[++i]; - i++ + if (!p[++i]) + continue + word += p[i++] continue + case '$': + word += p[i++] + c = p[i] + if (c == '0') + gene.curfont = gene.deffont + else if (c >= '1' && c <= '9') + gene.curfont = get_font("u" + c) + // fall thru default: word += p[i++] continue } break @@ -221,138 +231,163 @@ } break } /* store the word in the next note */ - while (s && (s.type != C.NOTE || s.grace)) + while (s && s.type != C.NOTE) s = s.next if (!s) { syntax(1, "Too many words in lyric line") return } if (word - && s.pos.voc != C.SL_HIDDEN) { - if (word.match(/^\$\d/)) { - gene.curfont = word[1] == '0' ? - dfnt : - get_font("u" + word[1]) - word = word.slice(2) - } + && (s.pos.voc & 0x07) != C.SL_HIDDEN) { ly = { t: word, - font: gene.curfont, - wh: strwh(word), + font: cf, istart: j, iend: j + word.length } + if (ln) + ly.ln = ln if (!s.a_ly) s.a_ly = [] s.a_ly[curvoice.lyric_line] = ly + cf = gene.curfont } s = s.next; i++ } curvoice.lyric_cont = s } -// -- set the width needed by the lyrics -- -// (called once per tune) -function ly_width(s, wlw) { - var ly, sz, swfac, align, xx, w, i, j, k, shift, p, - a_ly = s.a_ly; - +// install the words under a note +// (this function is called during the generation) +function ly_set(s) { + var i, j, ly, d, s1, s2, p, w, spw, xx, sz, shift, dw, + s3 = s, // start of the current time sequence + wx = 0, + wl = 0, + n = 0, + dx = 0, + a_ly = s.a_ly, align = 0 + + // get the available horizontal space before the next lyric words + for (s2 = s.ts_next; s2; s2 = s2.ts_next) { + if (s2.shrink) { + dx += s2.shrink + n++ // number of symbols without word + } + if (s2.bar_type) { // stop on a bar + dx += 3 // and take some of its spacing + break + } + if (!s2.a_ly) + continue + i = s2.a_ly.length + while (--i >= 0) { + ly = s2.a_ly[i] + if (!ly) + continue + if (ly.ln != 2) + break + } + if (i >= 0) + break + } + + // define the offset of the words for (i = 0; i < a_ly.length; i++) { ly = a_ly[i] if (!ly) continue - p = ly.t; - if (p == "-\n" || p == "_\n") { + gene.curfont = ly.font + ly.t = p = str2svg(ly.t) + if (ly.ln == 2) { ly.shift = 0 continue } - w = ly.wh[0]; - swfac = ly.font.swfac; - xx = w + 2 * cwid(' ') * swfac - if (s.type == C.GRACE) { // %%graceword + spw = cwid(' ') * ly.font.swfac + w = p.wh[0] + spw * 1.5 + 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] == ')') { + || p[1] == ':' + || p[0] == '(' || p[0] == ')') { if (p[0] == '(') { - sz = cwid('(') * swfac + sz = spw } else { j = p.indexOf(' '); // ( ) set_font(ly.font) if (j > 0) sz = strwh(p.slice(0, j))[0] else sz = w * .2 } - shift = (w - sz + 2 * cwid(' ') * swfac) * .4 - if (shift > 20) - shift = 20; + w -= sz + shift = w * .4 + if (shift > 14) + shift = 14 shift += sz - if (ly.t[0] >= '0' && ly.t[0] <= '9') { + if (p[0] >= '0' && p[0] <= '9') { if (shift > align) align = shift } } else { - shift = xx * .4 - if (shift > 20) - shift = 20 + shift = w * .4 + if (shift > 14) + shift = 14 } ly.shift = shift - if (wlw < shift) - wlw = shift; -// if (p[p.length - 1] == "\n") // if "xx-" -// xx -= cwid(' ') * swfac - xx -= shift; - shift = 2 * cwid(' ') * swfac - for (k = s.next; k; k = k.next) { - switch (k.type) { - case C.NOTE: - case C.REST: - if (!k.a_ly || !k.a_ly[i]) - xx -= 9 - else if (k.a_ly[i].t == "-\n" - || k.a_ly[i].t == "_\n") - xx -= shift - else - break - if (xx <= 0) - break - continue - case C.CLEF: - case C.METER: - case C.KEY: - xx -= 10 - continue - default: - xx -= 5 - break - } - break - } - if (xx > s.wr) - s.wr = xx - } + if (shift > wl) + wl = shift // max left space + w -= shift // right width + if (w > wx) + wx = w // max width + } + + // set the left space + while (!s3.seqst) + s3 = s3.ts_prev + if (s3.ts_prev && s3.ts_prev.bar_type) + wl -= 4 // don't move too much the measure bar + if (s3.wl < wl) { + s3.shrink += wl - s3.wl + s3.wl = wl + } + + // if not room enough, shift the following notes to the right + dx -= 6 + if (dx < wx) { + dx = (wx - dx) / n + s1 = s.ts_next + while (1) { + if (s1.shrink) { + s1.shrink += dx + s3.wr += dx // (needed for end of line) + s3 = s1 + } + if (s1 == s2) + break + s1 = s1.ts_next + } + } + if (align > 0) { for (i = 0; i < a_ly.length; i++) { ly = a_ly[i] if (ly && ly.t[0] >= '0' && ly.t[0] <= '9') ly.shift = align } } - return wlw -} +} // ly_set() /* -- draw the lyrics under (or above) notes -- */ /* (the staves are not yet defined) */ -/* !! this routine is tied to ly_width() !! */ function draw_lyric_line(p_voice, j, y) { - var p, lastx, w, s, s2, ly, lyl, + var p, lastx, w, s, s2, ly, lyl, ln, hyflag, lflag, x0, font, shift if (p_voice.hy_st & (1 << j)) { hyflag = true; p_voice.hy_st &= ~(1 << j) @@ -381,43 +416,40 @@ continue } if (ly.font != gene.curfont) /* font change */ gene.curfont = font = ly.font; p = ly.t; - w = ly.wh[0]; + w = p.wh[0] shift = ly.shift if (hyflag) { - if (p == "_\n") { /* '_' */ - p = "-\n" - } else if (p != "-\n") { /* not '-' */ + if (ly.ln == 2 && p == "_") { // '_' + p = "-" + } else if (ly.ln != 2) { // not '-' out_hyph(lastx, y, s.x - shift - lastx); hyflag = false; lastx = s.x + s.wr } } if (lflag - && p != "_\n") { /* not '_' */ + && (ly.ln != 2 || p != "_")) { // not '_' out_wln(lastx + 3, y, x0 - lastx + 3); lflag = false; lastx = s.x + s.wr } - if (p == "-\n" /* '-' */ - || p == "_\n") { /* '_' */ + if (ly.ln == 2) { // '-' or '_' if (x0 == 0 && lastx > s.x - 18) lastx = s.x - 18 - if (p[0] == '-') + if (p == '-') hyflag = true else lflag = true; x0 = s.x - shift continue } x0 = s.x - shift; - if (p.slice(-1) == '\n') { - p = p.slice(0, -1); /* '-' at end */ + if (ly.ln) // '-' at end hyflag = true - } if (user.anno_start || user.anno_stop) { s2 = { st: s.st, istart: ly.istart, iend: ly.iend, @@ -428,31 +460,31 @@ wl: 0, wr: w } anno_start(s2, 'lyrics') } - xy_str(x0, y, p, null, null, ly.wh); + xy_str(x0, y, p) anno_stop(s2, 'lyrics') lastx = x0 + w } if (hyflag) { hyflag = false; x0 = realwidth - 10 if (x0 < lastx + 10) x0 = lastx + 10; out_hyph(lastx, y, x0 - lastx) - if (cfmt.hyphencont) + if (p_voice.s_next && p_voice.s_next.fmt.hyphencont) p_voice.hy_st |= (1 << j) } /* see if any underscore in the next line */ - for (p_voice.s_next ; s; s = s.next) { + 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.t == "_\n") { + if (ly && ly.ln && ly.t == "_") { lflag = true; x0 = realwidth - 15 if (x0 < lastx + 12) x0 = lastx + 12 } @@ -470,22 +502,22 @@ var j, top, sc = staff_tb[p_voice.st].staffscale; set_font("vocal") if (incr > 0) { /* under the staff */ - if (y > -cfmt.vocalspace) - y = -cfmt.vocalspace; + 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) } return (y - a_h[j - 1] / 6) / sc } /* above the staff */ - top = staff_tb[p_voice.st].topbar + cfmt.vocalspace + 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); @@ -496,11 +528,11 @@ // -- draw all the lyrics -- /* (the staves are not yet defined) */ function draw_all_lyrics() { var p_voice, s, v, nly, i, x, y, w, a_ly, ly, - lyst_tb = new Array(nstaff+ 1), + lyst_tb = new Array(nstaff + 1), nv = voice_tb.length, h_tb = new Array(nv), nly_tb = new Array(nv), above_tb = new Array(nv), rv_tb = new Array(nv), @@ -532,11 +564,11 @@ w = 10 for (i = 0; i < a_ly.length; i++) { ly = a_ly[i] if (ly) { x -= ly.shift; - w = ly.wh[0] + w = ly.t.wh[0] break } } y = y_get(p_voice.st, 1, x, w) if (top < y) @@ -549,12 +581,12 @@ for (i = 0; i < a_ly.length; i++) { ly = a_ly[i] if (!ly) continue if (!h_tb[v][i] - || ly.font.size > h_tb[v][i]) - h_tb[v][i] = ly.font.size + || ly.t.wh[1] > h_tb[v][i]) + h_tb[v][i] = ly.t.wh[1] } } } else { y = y_get(p_voice.st, 1, 0, realwidth) if (top < y) @@ -569,11 +601,11 @@ lyst_tb[st].bot = bot; nly_tb[v] = nly if (nly == 0) continue if (p_voice.pos.voc) - above_tb[v] = p_voice.pos.voc == C.SL_ABOVE + above_tb[v] = (p_voice.pos.voc & 0x07) == C.SL_ABOVE else if (voice_tb[v + 1] /*fixme:%%staves:KO - find an other way..*/ && voice_tb[v + 1].st == st && voice_tb[v + 1].have_ly) above_tb[v] = true Index: core/mei.js ================================================================== --- core/mei.js +++ core/mei.js @@ -1,8 +1,8 @@ // abc2svg - mei.js - MEI front-end // -// Copyright (C) 2019-2020 Jean-Francois Moine +// Copyright (C) 2019-2021 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,11 @@ // You should have received a copy of the GNU Lesser General Public License // along with abc2svg-core. If not, see . // convert the xml source into a tree function xml2tree(xml) { - var i, j, item, n, no, an, + var i, j, item, n, no, e = 0, stack = [], root = { name: "root" }, @@ -66,31 +66,35 @@ o = stack.pop() if (!o) error(1, null, 'No start tag "' + item + '"') continue } - item = xml.slice(i + 1, e) - n = item.match(/(\w+)[\s]*([^]*)/) + item = xml.slice(i + 1, e).trim() no = { - name: n[1], + name: item.match(/[\w-]+/)[0], ix: i } if (!o.children) o.children = [] o.children.push(no) - if (n[2].slice(-1) != '/') { // if some content + if (item.slice(-1) != '/') { // if some content stack.push(o) o = no } else { - n[2] = n[2].slice(0, -1) + item = item.slice(0, -1).trim() } - if (!n[2]) + item = item.replace(/[^\s]*\s*/, '') + if (!item) continue - n = n[2].match(/[^\s"=]+=?|".*?"/g) // " - for (j = 0; j < n.length; j += 2) { - an = n[j].slice(0, -1) - no[an] = n[j + 1].slice(1, -1) + n = item.match(/=|[^\s"=]+|".*?"/g) // " + for (j = 0; j < n.length; j += 3) { + if (n[j + 1] != '=' + || !n[j + 2]) { + mus.error(1, null, 'Bad key=value in "' + item + '"') + break + } + no[n[j]] = n[j + 2].slice(1, -1) } } return root } @@ -1193,11 +1197,12 @@ curr.text += tag.content.replace(/[ \t\n]+/g, ' ') }, // tie tie: function(tag) { - var s1 = get_ref(tag), + var m1, m2, not1, not2, + s1 = get_ref(tag), s2 = get_ref(tag, s1), ty = C.SL_AUTO if (!s1 || !s2) return @@ -1207,33 +1212,31 @@ } switch (tag.lform) { case "dashed": case "dotted": ty |= C.SL_DOTTED; break } - if (s1.s) { - s1.tie_ty = ty - if (s2.s) { // note - note - s1.s.tie_s = s2.s - s1.tie_n = s2 - s2.s.ti2 = s1.s - } else { // note - single note - s1.s.tie_s = s2 - s1.tie_n = s2.notes[0] - s2.ti2 = s1.s - } - } else { - s1.notes[0].tie_ty = ty - if (s2.s) { // single note - note - s1.tie_s = s2.s - s1.notes[0].tie_n = s2 - s2.s.ti2 = s1 - } else { // single note - single note - s1.tie_s = s2 - s1.notes[0].tie_n = s2.notes[0] - s2.ti2 = s1 - } - } + if (s1.s) { // note - note + not1 = s1 + s1 = s1.s + not2 = s2 + s2 = s2.s + not1.tie_ty = ty + not1.tie_e = not2 + not2.tie_s = not1 + } else { // chord - chord + for (m1 = 0; m1 <= s1.nhd; m1++) { + not1 = s1.notes[m1] + for (m2 = 0; m2 <= s2.nhd; m2++) { + not2 = s2.notes[m2] + if (not1.pit == not2.pit) { + not1.tie_ty = ty + not1.tie_e = not2 + not2.tie_s = not1 + } + } + } + s1.ti1 = s2.ti2 = true }, // tie() trill: function(tag) { var s2, s = get_ref(tag) Index: core/modules.js ================================================================== --- core/modules.js +++ core/modules.js @@ -42,13 +42,15 @@ grid2: {}, jazzchord: {}, jianpu: {}, mdnn: {}, MIDI: {}, + nns: {}, pageheight: { fn: 'page' }, pedline: {}, percmap: { fn: 'perc' }, + roman: {}, soloffs: {}, sth: {}, strtab: {}, temperament: { fn: 'temper' }, Index: core/music.js ================================================================== --- core/music.js +++ core/music.js @@ -1,8 +1,8 @@ // abc2svg - music.js - music generation // -// Copyright (C) 2014-2021 Jean-Francois Moine +// Copyright (C) 2014-2022 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -20,22 +20,13 @@ var gene, staff_tb, nstaff, // current number of staves tsnext, // next line when cut realwidth, // real staff width while generating - insert_meter, // insert time signature (1) and indent 1st line (2) + insert_meter, // insert the time signature spf_last, // spacing for last short line -/* width of notes indexed by log2(note_length) */ - space_tb = new Float32Array([ -// factor 1.3 instead of 1.414 and crotchet 30px instead of 40px -// change again: -// factor 1.33 instead of 1.3 and crotchet 35px instead of 30px - 8.4, 11.2, 14.9, 19.8, 26.3, - 35, - 46.6, 61.9, 82.3, 109.5 - ]), smallest_duration /* -- decide whether to shift heads to other side of stem on chords -- */ /* this routine is called only once per tune */ @@ -83,11 +74,11 @@ if (dur % 12 != 0) error(1, s, "Invalid note duration $1", dur); dur /= 12 /* see C.BLEN for values */ if (!dur) error(1, s, "Note too short") - for (flags = 5; dur != 0; dur >>= 1, flags--) { + for (flags = 5; dur; dur >>= 1, flags--) { if (dur & 1) break } dur >>= 1 if ((dur + 1) & dur) { @@ -107,11 +98,11 @@ /* fall thru */ case -4: head = C.SQUARE break case -3: - head = cfmt.squarebreve ? C.SQUARE : C.OVALBARS + head = s.fmt.squarebreve ? C.SQUARE : C.OVALBARS break case -2: head = C.OVAL break case -1: @@ -129,13 +120,13 @@ if (!n) return // single note /* set the head shifts */ - dx = dx_head * .78 + dx = dx_head * .74 if (s.grace) - dx *= .5 + dx *= .6 if (dir >= 0) { i1 = 1; i2 = n + 1; ps = s.notes[0].pit } else { @@ -247,11 +238,13 @@ if (dx1 > dx2) dx2 = dx1 // align the accidentals notes[i1].shac = notes[i2].shac = dx2 } else { notes[i1].shac = dx1 - notes[i2].shac = dx2 = dx1 + 7 + if (p1 != ps) + dx1 += 7 // (there is a shift of a head when unison) + notes[i2].shac = dx2 = dx1 } dx2 += 7 // shift the remaining accidentals for (i = i1; --i > i2; ) { // from top to bottom @@ -337,10 +330,12 @@ else s.p_v.sym = s; next.prev = s } function lktsym(s, next) { // time linkage + var old_wl + s.ts_next = next if (next) { s.ts_prev = next.ts_prev if (s.ts_prev) s.ts_prev.ts_next = s; @@ -350,23 +345,29 @@ error(2, s, "Bad linkage") 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] != 0) - || s.ts_prev.v == s.v) // case cut bar + || (w_tb[s.ts_prev.type] != w_tb[s.type] + && w_tb[s.ts_prev.type]) if (!next) return next.seqst = next.time != s.time || (w_tb[s.type] != w_tb[next.type] - && w_tb[s.type] != 0) + && w_tb[s.type]) if (next.seqst) { + old_wl = next.wl self.set_width(next) - next.shrink = next.wl - if (next.prev) - next.shrink += next.prev.wr + if (next.a_ly) + ly_set(next) + if (!next.shrink) { + next.shrink = next.wl + if (next.prev) + next.shrink += next.prev.wr + } else { + next.shrink += next.wl - old_wl + } next.space = 0 } } /* -- unlink a symbol -- */ @@ -439,10 +440,14 @@ /* link in time */ while (!s.seqst) s = s.ts_prev; lktsym(new_s, s) + if (s.soln) { // move the start of line + new_s.soln = true + delete s.soln + } return new_s } /* -- set the staff of the floating voices -- */ /* this function is called only once per tune */ @@ -532,35 +537,33 @@ } /* -- set the x offset of the grace notes -- */ function set_graceoffs(s) { var next, m, dx, x, - gspleft = cfmt.gracespace[0], - gspinside = cfmt.gracespace[1], - gspright = cfmt.gracespace[2], + gspleft = s.fmt.gracespace[0], + gspinside = s.fmt.gracespace[1], + gspright = s.fmt.gracespace[2], g = s.extra; if (s.prev && s.prev.type == C.BAR) gspleft -= 3; x = gspleft; g.beam_st = true for ( ; ; g = g.next) { set_head_shift(g) - acc_shift(g.notes, 7); + acc_shift(g.notes, 6.5) dx = 0 for (m = g.nhd; m >= 0; m--) { - if (g.notes[m].shac > dx) - dx = g.notes[m].shac + if (g.notes[m].shac - 2 > dx) + dx = g.notes[m].shac - 2 } x += dx; g.x = x - if (g.nflags <= 0) { - g.beam_st = true; - g.beam_end = true - } + if (g.nflags <= 0) + g.beam_st = g.beam_end = true next = g.next if (!next) { g.beam_end = true break } @@ -591,34 +594,40 @@ /* return the whole width */ return x } // Compute the smallest spacing between symbols according to chord symbols -// so that they stay at the same offset. +// so that they stay at the same offset +// and, also, adjust the spacing due to the lyric words. // Constraints: // - assume the chord symbols are only in the first staff // - treat only the first chord symbol of each symbol // - the chord symbols under the staff are ignored function set_w_chs(s) { var i, ch, w0, s0, dw, x = 0, n = 0 + set_font("vocal") for ( ; s; s = s.ts_next) { if (s.shrink) { x += s.shrink; n++ } + if (s.a_ly) // if some lyric + ly_set(s) + if (!s.a_gch) continue for (i = 0; i < s.a_gch.length; i++) { ch = s.a_gch[i] if (ch.type != 'g' || ch.y < 0) // upper chord symbol only continue if (w0) { // width of the previous chord symbol if (w0 > x + ch.x) { if (s.prev // (if not at start of a secondary voice) + && s.prev.seqst && s.prev.type == C.BAR) // don't move away n-- // the symbol from a bar dw = (w0 - x - ch.x) / n while (1) { s0 = s0.ts_next @@ -670,11 +679,11 @@ /* This routine sets the minimal left and right widths wl,wr * so that successive symbols are still separated when * no extra glue is put between them */ // (possible hook) Abc.prototype.set_width = function(s) { - var s2, i, m, xx, w, wlnote, wlw, acc, + var s2, i, m, xx, w, wlnote, wlw, acc, nt, bar_type, meter, last_acc, n1, n2, esp, tmp if (s.play) { // if play symbol s.wl = s.wr = 0 return @@ -689,11 +698,11 @@ /* room for shifted heads and accidental signs */ if (s.xmx > 0) s.wr += s.xmx + 4; for (s2 = s.prev; s2; s2 = s2.prev) { - if (w_tb[s2.type] != 0) + if (w_tb[s2.type]) break } if (s2) { switch (s2.type) { case C.BAR: @@ -700,25 +709,31 @@ case C.CLEF: case C.KEY: case C.METER: wlnote += 3 break + case C.STBRK: + wlnote += 8 + break } } for (m = 0; m <= s.nhd; m++) { - xx = s.notes[m].shhd + nt = s.notes[m] + xx = nt.shhd if (xx < 0) { if (wlnote < -xx + 5) wlnote = -xx + 5 } - acc = s.notes[m].acc + acc = nt.acc if (acc) { - tmp = s.notes[m].shac + - (typeof acc != "number" ? 5.5 : 3.5) + tmp = nt.shac + + (typeof acc == "object" ? 5.5 : 3.5) if (wlnote < tmp) wlnote = tmp } + if (nt.a_dd) // if decoration in chord + wlnote += deco_wch(nt) } if (s2) { switch (s2.type) { case C.BAR: case C.CLEF: @@ -801,14 +816,11 @@ /* leave room for guitar chord */ if (s.a_gch) wlw = gchord_width(s, wlnote, wlw) - /* leave room for vocals under note */ - /* related to draw_lyrics() */ - if (s.a_ly) - wlw = ly_width(s, wlw) + // ignore the lyrics for now /* if preceeded by a grace note sequence, adjust */ if (s2 && s2.type == C.GRACE) s.wl = wlnote - 4.5 else @@ -826,19 +838,22 @@ case C.BAR: bar_type = s.bar_type switch (bar_type) { case "|": w = 5 // 3 + 2 + break + case "[": // repeat number on secondary staff + w = 0 break default: w = 2 + 2.8 * bar_type.length for (i = 0; i < bar_type.length; i++) { switch (bar_type[i]) { case "[": case "]": - w += 3 - break + w += 1 + // fall thru case ":": w += 2 break } } @@ -852,13 +867,15 @@ s.wr = 5 // s.notes[0].shhd = (w - 5) * -.5 /* if preceeded by a grace note sequence, adjust */ for (s2 = s.prev; s2; s2 = s2.prev) { - if (w_tb[s2.type] != 0) { + if (w_tb[s2.type]) { if (s2.type == C.GRACE) - s.wl -= 8 + s.wl -= 6 + if (s2.type == C.STBRK) + s.wl -= 12 break } } if (s.a_dd) @@ -880,25 +897,27 @@ s.wl = s.wr = 1 // (!! not 0 !!) return } s.wl = s.clef_small ? 11 : 12 s.wr = s.clef_small ? 10 : 12 + if (s.prev && s.prev.type == C.STBRK) { + s.wl -= 6 + delete s.next.clef_small + } if (s.next && s.next.type == C.BAR) s.wr -= 4 return case C.KEY: - if ((!s.k_a_acc && !s.k_sf && !s.k_old_sf) // if no accidental - || s.k_none // or no key - || s.k_play) { // or key for play + if (s.invis) { // if no accidental s.wl = s.wr = 0 // no width return } s.wl = 3; esp = 4 if (!s.k_a_acc) { n1 = s.k_sf /* new key sig */ - if (s.k_old_sf && (cfmt.cancelkey || n1 == 0)) + if (s.k_old_sf && (s.fmt.cancelkey || n1 == 0)) n2 = s.k_old_sf /* old key */ else n2 = 0 if (n1 * n2 >= 0) { /* if no natural */ if (n1 < 0) @@ -981,29 +1000,23 @@ if (s.invis) break s.wl = set_graceoffs(s); s.wr = 0 if (s.a_ly) - ly_width(s, wlw) + ly_set(s) return case C.STBRK: s.wl = s.xmx - if (s.next && s.next.type == C.CLEF) { - s.wr = 2 - delete s.next.clef_small /* big clef */ - } else { - s.wr = 8 - } + s.wr = 8 return case C.CUSTOS: s.wl = s.wr = 4 return case C.TEMPO: // no width, but build the tempo string tempo_build(s) break case C.BLOCK: // no width - case C.PART: case C.REMARK: case C.STAVES: break default: error(2, s, "set_width - Cannot set width for symbol $1", s.type) @@ -1046,18 +1059,18 @@ i = 1 else i = 0 } l = len - ((C.BLEN / 16 / 8) << i) - space = space_tb[i] + space = cfmt.spatab[i] if (l) { if (l < 0) { - space = space_tb[0] * len / (C.BLEN / 16 / 8) + space = cfmt.spatab[0] * len / (C.BLEN / 16 / 8) } else { if (i >= 9) i = 8 - space += (space_tb[i + 1] - space_tb[i]) * + space += (cfmt.spatab[i + 1] - cfmt.spatab[i]) * l / ((C.BLEN / 16 / 8) << i) } } return space } @@ -1075,12 +1088,12 @@ ///*fixme:do same thing at start of line*/ // case C.NOTE: // case C.REST: // if (s.ts_prev.type == C.BAR) { // if (s.nflags < -2) -// return space_tb[0] -// return space_tb[2] +// return cfmt.spatab[0] +// return cfmt.spatab[2] // } // break } return 0 } @@ -1093,15 +1106,16 @@ while (!s.dur) { switch (s.type) { case C.BAR: // (hack to have quite the same note widths between measures) + if (!s.next) + space *= .9 return space * .9 - 7 case C.CLEF: return space - s.wl - s.wr case C.BLOCK: // no space - case C.PART: case C.REMARK: case C.STAVES: case C.TEMPO: s = s.ts_next if (!s) @@ -1199,57 +1213,58 @@ iend: s.iend, v: s.v, p_v: s.p_v, st: s.st, dur: 0, + time: s.time + (s.dur || 0), nhd: 0, notes: [{ pit: s.notes ? s.notes[0].pit : 22 }], - prev: s + seqst: true, + invis: true, + prev: s, + fmt: s.fmt } } // _bar() // create an invisible bar for end of music lines function add_end_bar(s) { var b = _bar(s) - b.seqst = true - b.invis = true - b.time = s.time + s.dur / 2 b.wl = 0 b.wr = 0 b.ts_prev = s b.next = s.next b.ts_next = s.ts_next b.shrink = s.wr + 3 - if (s.next) + if (s.next) // (must not be the end of the voice) s.next.prev = b - if (s.ts_next) +// if (s.ts_next) s.ts_next.ts_prev = b s.next = s.ts_next = b - b.space = set_space(b, s.time) + b.space = set_space(b, s.time + s.dur * .4) return b } /* -- set the width and space of all symbols -- */ // this function is called once for the whole tune function set_allsymwidth() { - var maxx, val, st, s_chs, tim, stup, + var val, st, s_chs, stup, s = tsfirst, s2 = s, xa = 0, xl = [], - wr = [] - - /* loop on all symbols */ - maxx = xa + wr = [], + maxx = xa, tim = s.time + + /* loop on all symbols */ while (1) { do { - if (s.a_gch && !s_chs) + if ((s.a_gch || s.a_ly) && !s_chs) s_chs = s; self.set_width(s); st = s.st if (xl[st] == undefined) xl[st] = 0 @@ -1262,10 +1277,15 @@ } while (s && !s.seqst); // set the spaces of the time sequence s2.shrink = maxx - xa s2.space = s2.ts_prev ? set_space(s2, tim) : 0 + + // adjust the spacing when after a spacer (y) + if (s2.space == 0 && s2.ts_prev + && s2.ts_prev.type == C.SPACE && s2.ts_prev.seqst) + s2.space = s2.ts_prev.space /= 2 if (s2.dur && s2.dur != s2.notes[0].dur) { if (!stup) { stup = s2 while (stup.prev @@ -1300,10 +1320,11 @@ s2 = s2.ts_next } while (!s2.seqst) } // let the chord symbols at the same offset + // and adjust the spacing due to the lyrics if (s_chs) set_w_chs(s_chs) } // insert a rest, this one replacing a sequence or a measure @@ -1545,22 +1566,24 @@ // p_voice.sym = null; p_voice.time = s.time; new_s = sym_add(p_voice, C.CUSTOS); new_s.next = s; s.prev = new_s; + new_s.wl = 0 // (needed here for lktsym) + new_s.wr = 4 lktsym(new_s, s); new_s.shrink = s.shrink if (new_s.shrink < 8 + 4) new_s.shrink = 8 + 4; new_s.space = s2.space; - new_s.wl = 0; - new_s.wr = 4; + new_s.head = C.FULL + new_s.stem = s2.stem new_s.nhd = s2.nhd; new_s.notes = [] - for (i = 0; i < s.notes.length; i++) { + for (i = 0; i < s2.notes.length; i++) { new_s.notes[i] = { pit: s2.notes[i].pit, shhd: 0, dur: C.BLEN / 4 } @@ -1573,11 +1596,11 @@ var p_voice, done, tim, ptyp // divide the left repeat (|:) or variant bars (|1) // the new bars go in the next line function bardiv(so) { // start of next line - var s, s1, s2, t1, t2, i + var s, s1, s2, t1, t2, i, noseq function new_type(s) { var t = s.bar_type.match(/(:*)([^:]*)(:*)/) // [1] = starting ':'s, [2] = middle, [3] = ending ':'s @@ -1585,13 +1608,12 @@ // |1 -> | [1 // :|]1 -> :|] [1 t1 = t[1] + t[2] t2 = '[' } else if (!t[1]) { // if left repeat only - // [|: -> | [|: - // ||: -> || [|: - t1 = t[2][0] == '[' ? t[2].slice(1) : t[2] + // x|: -> || [|: + t1 = '||' t2 = '[|' + t[3] } else { // :][: -> :|] [|: i = (t[2].length / 2) | 0 t1 = t[1] + '|' + t[2].slice(0, i) @@ -1603,51 +1625,66 @@ while (s.ts_prev && s.ts_prev.time == so.time) { s = s.ts_prev if (s.bar_type) s1 = s // first previous bar + if (s.type == C.GRACE) + so = s // keep the grace notes in the next line } if (!s1) return so // search the insertion point in the new line for (s = so; ; s = s.ts_next) { switch (s.type) { - default: - if (w_tb[s.type]) - break +// default: +// if (w_tb[s.type]) +// break case C.KEY: case C.METER: case C.CLEF: continue } break } + if (s.bar_type) + noseq = 1 + while (s1 != so) { if (s1.bar_type && (s1.bar_type.slice(-1) == ':' || s1.text) && s1.bar_type != ':') { new_type(s1) // return t1 and t2 s2 = clone(s1) s1.bar_type = t1 + delete s1.rbstop + delete s1.a_gch s2.bar_type = t2 lkvsym(s2, s1.next) // voice while (1) { if (s.type != C.BAR +//fixme: pb if inverted voices || s.v > s2.v) break s = s.ts_next } lktsym(s2, s) // time + if (!noseq) { + noseq = 1 + s2.seqst = true + s2.shrink = s2.wl + s2.prev.wr + s2.space = 0 + } if (s == so) so = s2 if (s1.text) { s2.invis = true delete s1.text delete s1.rbstart } + delete s1.part delete s2.a_dd } s1 = s1.ts_next } return so @@ -1670,23 +1707,21 @@ // advance in the next line for (s2 = s; s2; s2 = s2.ts_next) { switch (s2.type) { case C.KEY: - if (!cfmt.keywarn - || (!s2.k_a_acc && !s2.k_sf && !s2.k_old_sf) - || s2.k_none - || s2.k_play) // play only + if (!s.fmt.keywarn + || s2.invis) continue for (s1 = s.ts_prev; s1 ;s1 = s1.ts_prev) { if (s1.type != C.METER) break } // fall thru case C.METER: if (s2.type == C.METER) { - if (!cfmt.timewarn) + if (!s.fmt.timewarn) continue s1 = s.ts_prev } // fall thru case C.CLEF: @@ -1706,12 +1741,10 @@ // put the warning symbol at end of line s3 = clone(s2) // duplicate the K:/M:/clef lktsym(s3, s1.ts_next) // time link - if (s3.ts_next == s) - s.seqst = true // (M: eol M:) s1 = s3 while (1) { s1 = s1.ts_next if (s1.v == s2.v) @@ -1725,33 +1758,26 @@ if (s3.prev) // if not start of voice s3.shrink += s3.prev.wr s3.space = set_space(s3, s3.ts_prev.time) s3.next.shrink = s3.wr + s3.next.wl } + delete s3.part continue } if (w_tb[s2.type]) break // symbol with a width } } // do_warn() - // divide the left repeat and a variant bars + // divide the left repeat and variant bars s = bardiv(s) // add the warning symbols at the end of the previous line do_warn(s) /* if normal symbol, cut here */ - switch (s.ts_prev.type) { - case C.STAVES: - break - case C.GRACE: /* don't cut on a grace note */ - s = s.next - if (!s) - return s - /* fall thru */ - default: + if (s.ts_prev.type != C.STAVES) { set_eol(s) return s } /* go back to handle the staff breaks at end of line */ @@ -1774,11 +1800,11 @@ if (!s.ts_prev) return // null // no music yet if (s.ts_prev.type == C.BAR) break while (s.ts_next) { - if (w_tb[s.ts_next.type] != 0 + if (w_tb[s.ts_next.type] && s.ts_next.type != C.CLEF) break s = s.ts_next } if (!s.ts_next || s.ts_next.type != C.BAR) @@ -1832,20 +1858,20 @@ // two returned values: width of nice spacing, width with max shrinking function get_width(s, next) { var shrink, space, w = 0, wmx = 0, - sp_fac = (1 - cfmt.maxshrink) + sp_fac = (1 - s.fmt.maxshrink) while (s != next) { if (s.seqst) { shrink = s.shrink wmx += shrink if ((space = s.space) < shrink) w += shrink else - w += shrink * cfmt.maxshrink + w += shrink * s.fmt.maxshrink + space * sp_fac s.x = w } s = s.ts_next } @@ -1857,18 +1883,17 @@ /* -- search where to cut the lines according to the staff width -- */ function set_lines( s, /* first symbol */ next, /* symbol of the next line / null */ lwidth, /* w - (clef & key sig) */ indent) { /* for start of tune */ - var first, s2, s3, x, xmin, xmid, xmax, wwidth, shrink, space, ws, + var first, s2, s3, s4, x, xmin, xmid, xmax, wwidth, shrink, space, nlines, - last = next ? next.ts_prev : null - + last = next ? next.ts_prev : null, ws = get_width(s, next) // 2 widths: nice and shrinked // take care of big key signatures at end of line - if (cfmt.keywarn && next + if (s.fmt.keywarn && next && next.type == C.KEY && !last.dur) { ws[0] += next.wr ws[1] += next.wr } @@ -1891,19 +1916,20 @@ s2 = first = s; xmin = s.x - s.shrink - indent; xmax = xmin + lwidth; xmid = xmin + wwidth / nlines; - xmin += wwidth / nlines * cfmt.breaklimit; + xmin += wwidth / nlines * s.fmt.breaklimit; for (s = s.ts_next; s != next ; s = s.ts_next) { if (!s.x) continue if (s.type == C.BAR) s2 = s if (s.x >= xmin) break } + s4 = s // keep first symbol with x greater than xmin //fixme: can this occur? if (s == next) { if (next) next = set_nl(next) return next @@ -1930,62 +1956,57 @@ break } // no bar, try to avoid to cut a beam or a tuplet */ if (!s3) { - var beam = 0, - bar_time = s2.time; + s = s4 // restart after xmin + + var beam = 0, + bar_time = s2.time xmax -= 8; // (left width of the inserted bar in set_allsymwidth) - s = s2; // restart from start or last bar for ( ; s != next; s = s.ts_next) { - if (s.beam_st) - beam++ - if (s.beam_end && beam > 0) - beam-- + if (s.dur) { + if (!s.beam_end) + beam |= 1 << s.v + else + beam &= ~(1 << s.v) + } x = s.x if (!x) continue if (x + s.wr >= xmax) break if (beam || s.in_tuplet) continue -//fixme: this depends on the meter - if ((s.time - bar_time) % (C.BLEN / 4) == 0) { - s3 = s - continue - } - if (!s3 || s.x < xmid) { - s3 = s - continue - } - if (s3 > xmid) - break - if (xmid - s3.x < s.x - xmid) - break - s3 = s - break + if (s3 && s.x >= xmid) { + if (xmid - s3.x > s.x - xmid + || (s.dur + && (s.time + s.dur - bar_time) + % (C.BLEN / 4) == 0)) + s3 = s + break + } + s3 = s } } // cut anyhere if (!s3) { - s3 = s = s2 + s3 = s = s4 for ( ; s != next; s = s.ts_next) { x = s.x if (!x) continue - if (s.x < xmid) { - s3 = s - continue - } - if (s3 > xmid) - break - if (xmid - s3.x < s.x - xmid) - break - s3 = s - break + if (x + s.wr >= xmax) + break + if (s3 && s.x >= xmid) { + if (xmid - s3.x > s.x - xmid) + s3 = s + break + } + s3 = s } } s = s3 while (s.ts_next) { s = s.ts_next @@ -2042,22 +2063,22 @@ if (cfmt.custos && voice_tb.length == 1) lwidth -= 12 /* if asked, count the measures and set the EOLNs */ - if (cfmt.barsperstaff) { - i = cfmt.barsperstaff; + i = s.fmt.barsperstaff + if (i) { for (s2 = s; s2; s2 = s2.ts_next) { if (s2.type != C.BAR || !s2.bar_num || --i > 0) continue while (s2.ts_next && s2.ts_next.type == C.BAR) s2 = s2.ts_next if (s2.ts_next) s2.ts_next.soln = true - i = cfmt.barsperstaff + i = s.fmt.barsperstaff } } /* cut at explicit end of line, checking the line width */ s2 = s @@ -2080,13 +2101,13 @@ s = null } else if (!s.soln) { continue } else { s.soln = false + if (s.time == s2.time) + continue // empty music line! while (!s.seqst) -// || s.ts_prev.type == C.GRACE -// || s.ts_prev.type == C.SPACE) s = s.ts_prev } s2 = set_lines(s2, s, lwidth, indent) if (!s2) break @@ -2180,11 +2201,12 @@ // 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)"]; - deco_cnv([(start ? dc_st : dc_en)[2 + ottava]], s) + 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 @@ -2244,41 +2266,45 @@ function mrest_expand() { var s, s2 // expand a multi-rest into a set of rest + bar function mexp(s) { - var s2, s3, s4, next, tim, nbar, + var bar, s3, s4, tim, nbar, nb = s.nmes, - dur = s.dur / nb - - // change the multi-rest into a single rest - s.type = C.REST - s.dur = s.dur_orig = dur - s.nflags = -2 - s.head = C.FULL - - /* add the bar(s) and rest(s) */ - tim = s.time + dur - s2 = next = s.next // bar (always) + dur = s.dur / nb, + s2 = s.next + + // get the bar (there may be some other symbols before the bar) + while (!s2.bar_type) + s2 = s2.next + bar = s2 while (!s2.bar_num) // get the bar number s2 = s2.ts_prev nbar = s2.bar_num - s.nmes + // change the multi-rest into a single rest + s.type = C.REST + s.notes[0].dur = s.dur = s.dur_orig = dur + s.nflags = -2 + s.head = C.FULL + s.fmr = 1 // full measure rest + + /* add the bar(s) and rest(s) */ + tim = s.time + dur s3 = s2 = s while (--nb > 0) { // add the bar - if (next) { - s2 = clone(next) - delete s2.soln - delete s2.a_gch - delete s2.a_dd - lkvsym(s2, next) - } else { - s2 = _bar(s) // end of tune - s.next = s2 - } + s2 = clone(bar) + delete s2.soln + delete s2.a_gch + delete s2.a_dd + delete s2.text + delete s2.rbstart + delete s2.rbstop + lkvsym(s2, bar) + 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 @@ -2308,10 +2334,12 @@ // add the rest s4 = clone(s) delete s4.a_dd delete s4.soln + delete s4.a_gch + delete s4.part if (s2.next) { s4.next = s2.next s4.next.prev = s4 } else { s4.next = null @@ -2369,15 +2397,15 @@ } // mrest_expand() // set the clefs (treble or bass) in a 'auto clef' sequence // return the starting clef type function set_auto_clef(st, s_start, clef_type_start) { - var s, min, max, time, s2, s3; - - /* get the max and min pitches in the sequence */ - max = 12; /* "F," */ + var s, time, s2, s3, + max = 12, /* "F," */ min = 20 /* "G" */ + + /* get the max and min pitches in the sequence */ for (s = s_start; s; s = s.ts_next) { if (s.type == C.STAVES && s != s_start) break if (s.st != st) continue @@ -2440,13 +2468,13 @@ && s2.time == time && s2.type == C.NOTE && s2.notes[0].pit >= 19) /* F */ continue } else { - if (s.notes[0].pit < 12 /* F, */ + if (s.notes[0].pit <= 12 /* F, */ || s.notes[s.nhd].pit < 20) { /* G */ - if (s.notes[s.nhd].pit < 12) + if (s.notes[s.nhd].pit <= 12) s_last_chg = s continue } s2 = s.ts_prev if (s2 @@ -2515,14 +2543,14 @@ * (created here, updated on clef draw) * - voice_tb[v].clef = clefs at end of generation * (created on voice creation, updated here) */ function set_clefs() { - var s, s2, st, v, p_voice, g, new_type, new_line, p_staff, pit, - staff_clef = new Array(nstaff + 1), // st -> { clef, autoclef } - sy = cur_sy, - mid = [] + var s, s2, st, v, p_voice, g, new_type, new_line, p_staff, pit, + staff_clef = new Array(nstaff + 1), // st -> { clef, autoclef } + sy = cur_sy, + mid = [] // create the staff table staff_tb = new Array(nstaff + 1) for (st = 0; st <= nstaff; st++) { staff_clef[st] = { @@ -2548,11 +2576,11 @@ sy.staves[st].sep = sy.voices[v].sep if (sy.voices[v].maxsep) sy.staves[st].maxsep = sy.voices[v].maxsep; if (!p_voice.clef.clef_auto) - staff_clef[st].autoclef = false + delete staff_clef[st].autoclef } } for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] if (!sy.voices[v] @@ -2575,11 +2603,11 @@ if (s.repeat_n) set_repeat(s) switch (s.type) { case C.STAVES: - sy = s.sy + sy = s.sy // new system for (st = 0; st <= nstaff; st++) staff_clef[st].autoclef = true for (v = 0; v < voice_tb.length; v++) { if (!sy.voices[v]) continue @@ -3025,12 +3053,12 @@ } else { s.multi = 1 /* first voice(s) */ /* if 3 voices, and vertical space enough, * have stems down for the middle voice */ - if (i != 0 && i + 2 == st_v.length) { - if (st_v[i].ymn - cfmt.stemheight + if (i && i + 2 == st_v.length) { + if (st_v[i].ymn - s.fmt.stemheight >= st_v[i + 1].ymx) s.multi = -1; /* special case for unison */ t = s.ts_next @@ -3222,11 +3250,11 @@ lktsym(s, last_s) } /* -- init the symbols at start of a music line -- */ function init_music_line() { - var p_voice, s, s1, s2, s3, last_s, v, st, shr, shrmx, shl, + var p_voice, s, s1, s2, s3, last_s, v, st, shr, shrmx, shl, fmt, shlp, p_st, top, nv = voice_tb.length /* initialize the voices */ for (v = 0; v < nv; v++) { @@ -3236,12 +3264,14 @@ p_voice.second = cur_sy.voices[v].second; p_voice.last_sym = p_voice.sym; /* move the voice to a printed staff */ st = cur_sy.voices[v].st - while (st < nstaff && !cur_sy.st_print[st]) - st++; + while (st <= nstaff && !cur_sy.st_print[st]) + st++ + if (st > nstaff) + continue // no printed staff p_voice.st = st if (!p_voice.second) { staff_tb[st].key = p_voice.ckey staff_tb[st].meter = p_voice.meter } @@ -3248,10 +3278,11 @@ } // move the first clefs, key signatures and time signatures // to the staves s = tsfirst + fmt = s.fmt while (s) { switch (s.type) { case C.CLEF: case C.KEY: case C.METER: @@ -3262,16 +3293,18 @@ case C.KEY: s.p_v.ckey = s break case C.METER: s.p_v.meter = s - insert_meter |= 1 + insert_meter = cfmt.writefields.indexOf('M') >= 0 + && s.a_meter.length + if (!insert_meter && s.part) + s.next.part = s.part break } unlksym(s) // fall thru - case C.PART: case C.TEMPO: case C.BLOCK: case C.REMARK: s = s.ts_next continue @@ -3322,10 +3355,11 @@ delete s.clef_small; s.second = cur_sy.voices[v].second // (fixme: needed for sample5 X:3 Fugue & staffnonote.xhtml) if (!cur_sy.st_print[st]) s.invis = true + s.fmt = fmt } /* add keysig */ for (v = 0; v < nv; v++) { if (!cur_sy.voices[v] @@ -3337,27 +3371,29 @@ if (s2.k_sf || s2.k_a_acc) { s = clone(s2) new_sym(s, p_voice, last_s) delete s.invis s.k_old_sf = s2.k_sf // no key cancel + s.fmt = fmt } } /* add time signature (meter) if needed */ - if (insert_meter & 1) { + if (insert_meter) { for (v = 0; v < nv; v++) { p_voice = voice_tb[v]; s2 = p_voice.meter if (!cur_sy.voices[v] || cur_sy.voices[v].second - || !cur_sy.st_print[cur_sy.voices[v].st] - || !s2.a_meter.length) + || !cur_sy.st_print[cur_sy.voices[v].st]) +// || !s2.a_meter.length) continue s = clone(s2) new_sym(s, p_voice, last_s) + s.fmt = fmt } - insert_meter &= ~1 // no meter any more + insert_meter = false // no meter any more } // add an invisible bar for the various continued elements for (v = 0; v < nv; v++) { p_voice = voice_tb[v] @@ -3367,11 +3403,12 @@ fname: last_s.fname, bar_type: "|", dur: 0, multi: 0, invis: true, - sls: p_voice.sls + sls: p_voice.sls, + fmt: fmt } new_sym(s, p_voice, last_s) p_voice.sls = [] } } @@ -3380,24 +3417,14 @@ for (v = 0; v < nv; v++) { p_voice = voice_tb[v]; s2 = p_voice.bar_start; p_voice.bar_start = null - // if bar already, keep it in sequence - if (last_s && last_s.v == v) { - for (s3 = last_s; s3; s3 = s3.ts_next) { - if (s3.v != v) - break - switch (s3.type) { - case C.PART: - case C.TEMPO: - continue - case C.BAR: - p_voice.last_sym = s3 - last_s = s3.ts_next - break - } + // check if bracket stop at this time + for (s = last_s; s && s.time == last_s.time; s = s.ts_next) { + if (s.rbstop) { + s2 = null break } } if (!s2) @@ -3409,10 +3436,11 @@ if (p_voice.last_sym.type == C.BAR) { if (!p_voice.last_sym.rbstop) p_voice.last_sym.rbstart = 1 } else { new_sym(s2, p_voice, last_s) + s2.fmt = fmt } } // compute the spacing of the added symbols self.set_pitch(last_s); @@ -3439,12 +3467,16 @@ shlp = shl } // update the spacing before the first old time sequence shr = 0 + if (last_s.type == C.BAR && s2.type == C.CLEF) + shl += 4 // (see set_width) do { self.set_width(s) + if (s.a_ly) + ly_set(s) if (shr < s.wl) shr = s.wl; s = s.ts_next } while (s && !s.seqst); last_s.shrink = shl + shr @@ -3457,13 +3489,10 @@ s = tsfirst while (s.ts_next) s = s.ts_next if (s.type != C.BAR) { s2 = _bar(s) - s2.seqst = true - s2.invis = true - s2.time = s.time + (s.dur || 0) s2.ts_prev = s s.next = s.ts_next = s2 } } // check_end_bar() @@ -3489,11 +3518,13 @@ switch (s.type) { case C.MREST: start_flag = true break case C.BAR: - s.bar_type = bar_cnv(s.bar_type) + res = s.fmt.bardef[s.bar_type] + if (res) + s.bar_type = res if (!s.beam_on) start_flag = true if (!s.next && s.prev && !s.invis && s.prev.head == C.OVALBARS) @@ -3553,12 +3584,11 @@ if (lastnote) { lastnote.beam_end = true; lastnote = null } if (nflags <= 0) { - s.beam_st = true; - s.beam_end = true + s.beam_st = s.beam_end = true } else if (s.type == C.NOTE || s.beam_on) { s.beam_st = true; start_flag = false } } @@ -3607,20 +3637,20 @@ n = 0; s2 = null for (s = s.next; s; s = s.next) { if (s.type != C.BAR) continue - n++ if (s.rbstop) break if (!s.next) { s.rbstop = 2 // right repeat with end break } - if (n == cfmt.rbmin) + n++ + if (n == s.fmt.rbmin) s2 = s - if (n == cfmt.rbmax) { + if (n == s.fmt.rbmax) { if (s2) s = s2; s.rbstop = 1 // right repeat without end break } @@ -3636,10 +3666,12 @@ function set_global() { var p_voice, v, nv = voice_tb.length, sy = cur_sy, st = sy.nstaff + + insert_meter = cfmt.writefields.indexOf('M') >= 0 /* get the max number of staves */ while (1) { sy = sy.next if (!sy) @@ -3768,85 +3800,50 @@ if (beam) { s.stem = laststem } else if (s.beam_st && !s.beam_end) { // beam start beam = true; - // count the number of notes above the mid-line - n = pu = 0 - for (g = s; g; g = g.next) { - if (g.type != C.NOTE) { -// if (g.beam_end) -// break - continue - } - if (g.stem || g.multi) { // if forced direction - s.stem = g.stem || g.multi - break - } - n += g.nhd + 1 - for (m = 0; m <= g.nhd; m++) { - if (g.notes[m].pit >= mid_p) - pu++ - } - if (g.beam_end) - break - } - - if (!s.stem) { - pu *= 2 - if (pu > n) { - s.stem = -1 - } else if (pu < n) { - s.stem = 1 - - // same number of note above and below the mid-line - // get the highest and lowest pitches - } else { + // the stem direction is the one of the note + // farthest from the middle line pu = s.notes[s.nhd].pit; pd = s.notes[0].pit for (g = s.next; g; g = g.next) { - if (g.type != C.NOTE) { -// if (g.beam_end) -// break + if (g.type != C.NOTE) continue - } + if (g.stem || g.multi) // if forced direction + s.stem = g.stem || g.multi if (g.notes[g.nhd].pit > pu) pu = g.notes[g.nhd].pit if (g.notes[0].pit < pd) pd = g.notes[0].pit if (g.beam_end) break } - if (g.beam_end) { + if (!s.stem && g.beam_end) { if (pu + pd < mid_p * 2) { s.stem = 1 } else if (pu + pd > mid_p * 2) { s.stem = -1 } else { -//--fixme: equal: check all notes of the beam - if (cfmt.bstemdown) + if (s.fmt.bstemdown) s.stem = -1 } } - } - } if (!s.stem) s.stem = laststem } else { // no beam n = (s.notes[s.nhd].pit + s.notes[0].pit) / 2 - if (n == mid_p) { - for (m = 0; m <= s.nhd; m++) { + if (n == mid_p && s.nhd > 1) { + for (m = 0; m < s.nhd; m++) { if (s.notes[m].pit >= mid_p) break } n = m * 2 < s.nhd ? mid_p - 1 : mid_p + 1 } if (n < mid_p) s.stem = 1 - else if (n > mid_p) - s.stem = -1 - else if (cfmt.bstemdown) + else if (n > mid_p || s.fmt.bstemdown) s.stem = -1 else s.stem = laststem } } else { /* stem set by set_stem_dir */ @@ -3866,22 +3863,23 @@ } } // check if there may be one head for unison when voice overlap function same_head(s1, s2) { - var i1, i2, l1, l2, head, i11, i12, i21, i22, sh1, sh2 + var i1, i2, l1, l2, head, i11, i12, i21, i22, sh1, sh2, + shu = s1.fmt.shiftunison || 0 - if (s1.shiftunison && s1.shiftunison >= 3) + if (shu >= 3) return false if ((l1 = s1.dur) >= C.BLEN) return false if ((l2 = s2.dur) >= C.BLEN) return false if (s1.stemless && s2.stemless) return false if (s1.dots != s2.dots) { - if ((s1.shiftunison && (s1.shiftunison & 1)) + if (shu & 1 || s1.dots * s2.dots != 0) return false } if (s1.stem * s2.stem > 0) return false @@ -3957,13 +3955,13 @@ if (s2.dots) head = 2 else if (s1.dots) head = 1 } else if (l2 < C.BLEN / 4) { /* (l1 >= C.BLEN / 2) */ -// if ((s1.shiftunison && s1.shiftunison == 2) +// if (shu == 2) // || s1.dots != s2.dots) - if (s1.shiftunison && (s1.shiftunison & 2)) + if (shu & 2) return false head = s2.dur >= C.BLEN / 2 ? 2 : 1 } else { return false } @@ -3994,11 +3992,11 @@ acc = s2.notes[i2].acc if (!acc) { d = w_note[s2.head] * 2 + s2.xmx + s1.notes[i1].shac + 2 acc = s1.notes[i1].acc - if (typeof acc != "number") // microtone + if (typeof acc == "object") // microtone d += 2 if (s2.dots) d += 6 for (m = 0; m <= s1.nhd; m++) { s1.notes[m].shhd += d; @@ -4005,11 +4003,11 @@ s1.notes[m].shac -= d } s1.xmx += d } else { d = w_note[s1.head] * 2 + s1.xmx + s2.notes[i2].shac + 2 - if (typeof acc != "number") // microtone + if (typeof acc == "object") // microtone d += 2 if (s1.dots) d += 6 for (m = 0; m <= s2.nhd; m++) { s2.notes[m].shhd += d; @@ -4295,12 +4293,11 @@ s2.dot_low = true else s1.dot_low = false break case 0: - if (s1.notes[i1].acc != s2.notes[i2].acc - && !s1.notes[i1].acc && !s2.notes[i2].acc) { + if (s1.notes[i1].acc != s2.notes[i2].acc) { t = -1 break } if (s2.notes[i2].acc) { if (!s1.notes[i1].acc) @@ -4514,11 +4511,11 @@ if (s2.nflags > nflags) nflags = s2.nflags } /* set height of stem end */ - slen = cfmt.stemheight + slen = s.fmt.stemheight switch (nflags) { case 2: slen += 0; break case 3: slen += 4; break case 4: slen += 8; break case 5: slen += 12; break @@ -4605,11 +4602,10 @@ case "rightmargin": case "pagescale": case "pagewidth": case "scale": case "staffwidth": - blk_flush() self.set_format(s.subtype, s.param) break case "mc_start": // multicol start multicol = { posy: posy, @@ -4648,13 +4644,13 @@ case "ml": blk_flush() user.img_out(s.text) break case "newpage": - svg_flush() if (!user.page_format) break + blk_flush() if (blkdiv < 0) // split the tune user.img_out('') blkdiv = 2 // start the next SVG in a new page break case "sep": @@ -4665,10 +4661,11 @@ output += 'h' + s.l.toFixed(1) + '"/>\n'; vskip(s.sk2); break case "text": set_font(s.font) + use_font(s.font) write_text(s.text, s.opt) break case "title": write_title(s.text, true) break @@ -4763,11 +4760,11 @@ continue // no helper line } } // define the helper lines - p_staff.hll = 17 + i; // pitch of lowest note + p_staff.hll = 17 + i * 2 // pitch of lowest note // without helper line // ('D' when standard staff) p_staff.hlmap = new Int8Array(new Array((l - i + 1) * 2)) for (j = 1; i < l; i++, j += 2) { switch (p_staff.stafflines[i]) { @@ -4819,11 +4816,12 @@ // the block symbols will be treated after music line generation case C.BLOCK: if (!s.play) { blocks.push(s) unlksym(s) - } + } else if (s.ts_next && s.ts_next.shrink) + s.ts_next.shrink = 0 continue } st = s.st if (st > nstaff) { switch (s.type) { @@ -4924,16 +4922,15 @@ } /* -- position the symbols along the staff -- */ // (possible hook) Abc.prototype.set_sym_glue = function(width) { - var s, g, ll, + var s, g, ll, x, some_grace, spf, // spacing factor xmin = 0, // sigma shrink = minimum spacing xx = 0, // sigma natural spacing - x = 0, // sigma expandable elements xs = 0, // sigma unexpandable elements with no space xse = 0 // sigma unexpandable elements with space /* calculate the whole space of the symbols */ for (s = tsfirst; s; s = s.ts_next) { @@ -4965,26 +4962,27 @@ (tsnext.type == C.BLOCK // no, but followed by %%command && !tsnext.play) || blocks.length // (abcm2ps compatibility) // 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 = tsfirst; s; s = s.ts_next) { + 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 - cfmt.stretchlast)) - || (!ll && (xx + xs > width || cfmt.stretchstaff))) { + } 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); xx = 0; @@ -5010,17 +5008,19 @@ if (Math.abs(x - width) < 0.1) break } spf_last = spf } else { // shorter line - spf = (width - xs - xse) / xx - if (spf_last < spf) + spf = 1 - s.fmt.maxshrink + if (spf_last && xx * spf_last + xs < width) spf = spf_last - for (s = tsfirst; s; s = s.ts_next) { + x = 0 + for ( ; s; s = s.ts_next) { if (s.seqst) - x += s.space * spf <= s.shrink ? - s.shrink : s.space * spf + x += s.space <= s.shrink ? + s.shrink : + s.shrink * (1 - spf) + s.space * spf s.x = x } } realwidth = x @@ -5106,28 +5106,28 @@ // (possible hook) Abc.prototype.output_music = function() { var v, lwidth, indent, line_height, ts1st, tslast, p_v, nv = voice_tb.length - gen_init() - if (!tsfirst) - return set_global() if (nv > 1) // if many voices self.set_stem_dir() // set the stems direction in 'multi' for (v = 0; v < nv; v++) set_beams(voice_tb[v].sym); /* decide on beams */ self.set_stems() // set the stem lengths + + gen_init() // output the blocks and define the page layout + if (!tsfirst) + return + + set_acc_shft() // set the horizontal offset of accidentals if (nv > 1) { // if many voices set_rest_offset(); /* set the vertical offset of rests */ set_overlap(); /* shift the notes on voice overlap */ -// set_rp_bars() // set repeat bars } - set_acc_shft(); // set the horizontal offset of accidentals - set_allsymwidth(); /* set the width of all symbols */ indent = set_indent(true) /* if single line, adjust the page width */ @@ -5146,11 +5146,11 @@ ts1st = tsfirst v = nv while (--v >= 0) voice_tb[v].osym = voice_tb[v].sym - spf_last = 1.2 // last spacing factor + spf_last = 0 // last spacing factor while (1) { /* loop per music line */ set_piece(); self.set_sym_glue(lwidth - indent) if (realwidth) { if (indent) @@ -5159,14 +5159,12 @@ line_height = set_staff(); draw_systems(indent); draw_all_sym(); delayed_update(); vskip(line_height) - if (indent) { + if (indent) posx -= indent; - insert_meter &= ~2 // no more indentation - } while (blocks.length) self.block_gen(blocks.shift()) } blk_flush() @@ -5196,12 +5194,5 @@ if (p_v.sym && p_v.s_prev) p_v.sym.prev = p_v.s_prev p_v.sym = p_v.osym } } - -/* -- reset the generator -- */ -function reset_gen() { - insert_meter = cfmt.writefields.indexOf('M') >= 0 ? - 3 : /* insert meter and indent */ - 2 /* indent only */ -} Index: core/parse.js ================================================================== --- core/parse.js +++ core/parse.js @@ -1,8 +1,8 @@ // abc2svg - parse.js - ABC parse // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2021 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 @@ -16,11 +16,11 @@ // // You should have received a copy of the GNU Lesser General Public License // along with abc2svg-core. If not, see . var a_gch, // array of parsed guitar chords - a_dcn, // array of parsed decoration names + a_dcn = [], // array of parsed decoration names multicol, // multi column object maps = {} // maps object - see set_map() var qplet_tb = new Int8Array([ 0, 1, 3, 2, 3, 0, 2, 0, 3, 0 ]), ntb = "CDEFGABcdefgab" @@ -100,29 +100,33 @@ default: syntax(1, "Unknown clef '$1'", clef_def) return //undefined } if (clef_def[i] >= '1' && clef_def[i] <= '9') { - s.clef_line = Number(clef_def[i]); + s.clef_line = +clef_def[i] i++ } - if (clef_def[i + 1] != '8') + + // handle the octave (+/-8 - ^/_8) + delete curvoice.snd_oct + if (clef_def[i + 1] != '8' + && clef_def[i + 1] != '1') return s switch (clef_def[i]) { // octave case '^': s.clef_oct_transp = true case '+': - s.clef_octave = 7 - if (!s.clef_oct_transp) - curvoice.snd_oct = 12 // MIDI higher octave + s.clef_octave = clef_def[i + 1] == '8' ? 7 : 14 + if (!s.clef_oct_transp) // MIDI higher octave + curvoice.snd_oct = clef_def[i + 1] == 8 ? 12 : 24 break case '_': s.clef_oct_transp = true case '-': - s.clef_octave = -7 - if (!s.clef_oct_transp) - curvoice.snd_oct = -12 // MIDI lower octave + s.clef_octave = clef_def[i + 1] == '8' ? -7 : -14 + if (!s.clef_oct_transp) // MIDI lower octave + curvoice.snd_oct = clef_def[i + 1] == 8 ? -12 : -24 break } return s } @@ -140,10 +144,14 @@ syntax(1, errs.bad_transp) return } pit[i] = 242 // 'c' (C5) } else { + if (typeof note.acc == "object") { + syntax(1, errs.bad_transp) + return + } pit[i] = abc2svg.pab40(note.pit, note.acc) } } return pit[1] - pit[0] } @@ -240,11 +248,11 @@ if (!param) return if (/^[\]\[|.-]+$/.test(param)) return param.replace(/\]/g, '[') - var n = parseInt(param) + var n = +param switch (n) { case 0: return "..." case 1: return "..|" case 2: return ".||" case 3: return ".|||" @@ -260,22 +268,20 @@ type: C.BLOCK, subtype: subtype, dur: 0 } - if (parse.state == 2) - goto_tune() 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) return s } // set the voice parameters // (possible hook) Abc.prototype.set_vp = function(a) { - var s, item, pos, val, clefpit + var s, item, pos, val, clefpit, tr_p while (1) { item = a.shift() if (!item) break @@ -308,11 +314,11 @@ } } syntax(1, errs.bad_val, item) break case "octave=": - val = parseInt(a.shift()) + val = +a.shift() if (isNaN(val)) syntax(1, errs.bad_val, item) else curvoice.octave = val break @@ -324,46 +330,61 @@ // instrument=M/N => score=MN and sound=cN // (instrument=M == instrument=M/M) item = a.shift() val = item.indexOf('/') if (val < 0) { + val = get_interval('c' + item) + if (val == undefined) + break + curvoice.sndtran = val val = 0 - curvoice.sndtran = get_interval('c' + item) } else { - curvoice.sndtran = get_interval('c' + - item.slice(val + 1)) + val = get_interval('c' + item.slice(val + 1)) + if (val == undefined) + break + curvoice.sndtran = val val = get_interval(item.replace('/', '')) + if (val == undefined) + break } curvoice.transp = cfmt.sound ? curvoice.sndtran : val + tr_p = 1 break case "map=": // %%voicemap curvoice.map = a.shift() break case "name=": case "nm=": curvoice.nm = a.shift() if (curvoice.nm[0] == '"') - curvoice.nm = curvoice.nm.slice(1, -1); + curvoice.nm = cnv_escape(curvoice.nm.slice(1, -1)) curvoice.new_name = true break case "stem=": // compatibility case "pos=": // from %%pos only if (item == "pos=") - item = a.shift().split(' ') + item = a.shift() + .slice(1, -1) // always inside dble quotes + .split(' ') else item = ["stm", a.shift()]; val = posval[item[1]] if (val == undefined) { - syntax(1, errs.bad_val, item[0]) + syntax(1, errs.bad_val, "%%pos") break } + switch (item[2]) { + case "align": val |= C.SL_ALIGN; break + case "center": val |= C.SL_CENTER; break + case "close": val |= C.SL_CLOSE; break + } if (!pos) pos = {} pos[item[0]] = val break case "scale=": // %%voicescale - val = parseFloat(a.shift()) + val = +a.shift() if (isNaN(val) || val < .5 || val > 2) syntax(1, errs.bad_val, "%%voicescale") else curvoice.scale = val break @@ -375,30 +396,42 @@ // score=MN // (score=M == score=Mc) item = a.shift() if (cfmt.sound) break - curvoice.transp = get_interval(item, true) + val = get_interval(item, true) + if (val != undefined) { + curvoice.transp = val + tr_p = 1 + } break case "shift=": if (cfmt.nedo) { syntax(1, errs.notransp) break } - curvoice.shift = curvoice.sndsh = get_interval(a.shift()) + val = get_interval(a.shift()) + if (val != undefined) { + curvoice.shift = curvoice.sndsh = val + tr_p = 1 + } break case "sound=": if (cfmt.nedo) { syntax(1, errs.notransp) break } // concert-score display: apply sound= // sounding-score display: apply sound= only if M != c/C // sound: apply sound= - curvoice.sndtran = get_interval(a.shift()) + val = get_interval(a.shift()) + if (val == undefined) + break + curvoice.sndtran = val if (cfmt.sound) - curvoice.transp = curvoice.sndtran + curvoice.transp = val + tr_p = 1 break case "subname=": case "sname=": case "snm=": curvoice.snm = a.shift() @@ -413,18 +446,18 @@ par_sy.staves[curvoice.st].stafflines = val else curvoice.stafflines = val break case "staffnonote=": - val = parseInt(a.shift()) + val = +a.shift() if (isNaN(val)) syntax(1, "Bad %%staffnonote value") else curvoice.staffnonote = val break case "staffscale=": - val = parseFloat(a.shift()) + val = +a.shift() if (isNaN(val) || val < .3 || val > 2) syntax(1, "Bad %%staffscale value") else curvoice.staffscale = val break @@ -438,10 +471,11 @@ syntax(1, errs.bad_transp) } else { curvoice.sndtran = val if (cfmt.sound) curvoice.transp = val + tr_p = 1 } break default: switch (item.slice(0, 4)) { case "treb": @@ -474,10 +508,12 @@ if (clefpit) s.clefpit = clefpit get_clef(s) } } + if (tr_p) + set_transp() } // set_vp() // set the K: / V: parameters function set_kv_parm(a) { // array of items if (!curvoice.init) { // add the global parameters if not done yet @@ -564,40 +600,38 @@ // tonic i = 1 if (sf < -1) { switch (param[0]) { case 'H': // bagpipe + key_end = true if (param[1].toLowerCase() != 'p') { syntax(1, "Unknown bagpipe-like key") - key_end = true break } s.k_bagpipe = param[1]; sf = param[1] == 'P' ? 0 : 2; i++ // initialize the temperament if not done yet if (!cfmt.temper) // detune in cents for just intonation in A + // (from https://patrickmclaurin.com/wordpress/?page_id=2420) // C ^C D _E E F ^F G _A A _B B // 15.3 -14.0 -2.0 -10.0 1.9 13.3 -16.0 -31.8 -12.0 0.0 11.4 3.8 - // (C is ^C, F is ^F and G is =G) - // 86 84 - // temp = [100-14, -14, -2, -10, 2, 100-16, -16, -32, -12, 0, 11, 4] // but 'A' bagpipe = 480Hz => raise Math.log2(480/440)*1200 = 151 cfmt.temper = new Float32Array([ -// 2.37, 1.37, 1.49, 1.41, 1.53, 2.35, 1.35, 1.19, 1.39, 1.51, 1.62, 1.55 + // 1.66, 1.37, 1.49, 1.41, 1.53, 1.63, 1.35, 1.19, 1.39, 1.51, 1.62, 1.55 // C ^C D _E E F ^F G _A A _B B 11.62, 12.55, - 2.37, 2.37, 3.49, 0, - 2.37, 2.37, 3.49, 4.41, 5.53, 0, - 3.49, 4.41, 5.53, 7.35, 7.35, - 4.41, 5.53, 7.35, 7.35, 8.19, 0, - 7.35, 7.35, 8.19, 9.39, 10.51, 0, + 1.66, 2.37, 3.49, 0, + 1.66, 2.37, 3.49, 4.41, 5.53, 0, + 3.49, 4.41, 5.53, 6.63, 7.35, + 4.41, 5.53, 6.63, 7.35, 8.19, 0, + 6.63, 7.35, 8.19, 9.39, 10.51, 0, 8.19, 9.39, 10.51, 11.62, 12.55, 0, 10.51, 11.62, 12.55, - 2.37, 2.37 + 1.66, 1.66 ]) break case 'P': syntax(1, "K:P is deprecated"); sf = 0; @@ -699,11 +733,14 @@ while (--i >= 0) { note = s.k_a_acc[i] s.k_map[(note.pit + 19) % 7] = note.acc } } else { - s.k_map = abc2svg.keys[sf + 7] + s.k_map = s.k_bagpipe && !sf + ? abc2svg.keys[9] // implicit F# and C# + : abc2svg.keys[sf + 7] + } s.k_mode = mode // key note as base-40 s.k_b40 = [1,24,7,30,13,36,19, 2 ,25,8,31,14,37,20,3][sf + 7] @@ -711,11 +748,12 @@ return [s, info_split(param)] } // M: meter function new_meter(p) { - var s = { + var p_v, + s = { type: C.METER, dur: 0, a_meter: [] }, meter = {}, @@ -825,16 +863,16 @@ if (i >= p.length || p[i + 1] == '(') /* "M:5 (2/4+3/4)" */ break meter.top += p[i++] } - m1 = parseInt(meter.top) + m1 = +meter.top break } if (!in_parenth) { if (meter.bot) - m2 = parseInt(meter.bot); + m2 = +meter.bot wmeasure += m1 * C.BLEN / m2 } s.a_meter.push(meter); meter = {} while (p[i] == ' ') @@ -860,11 +898,11 @@ s.a_meter = [] if (parse.state != 3) { info.M = p; glovar.meter = s - if (parse.state >= 1) { + if (parse.state) { /* in the tune header, change the unit note length */ if (!glovar.ulen) { if (wmeasure <= 1 || wmeasure >= C.BLEN * 3 / 4) @@ -877,16 +915,18 @@ voice_tb[v].wmeasure = wmeasure } } } else { curvoice.wmeasure = wmeasure - if (is_voice_sig()) { - curvoice.meter = s; - reset_gen() - } else { + if (is_voice_sig()) + curvoice.meter = s + else sym_link(s) - } + + // 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 } } /* Q: tempo */ function new_tempo(text) { @@ -901,13 +941,13 @@ function get_nd(p) { var n, d, nd = p.match(/(\d+)\/(\d+)/) if (nd) { - d = Number(nd[2]) + d = +nd[2] if (d && !isNaN(d) && !(d & (d - 1))) { - n = Number(nd[1]) + n = +nd[1] if (!isNaN(n)) return C.BLEN * n / d } } syntax(1, "Invalid note duration $1", c) @@ -963,29 +1003,29 @@ nd = get_nd(text) if (!nd) return s.new_beat = nd } else { - s.tempo = Number(text) + s.tempo = +text if (!s.tempo || isNaN(s.tempo)) { syntax(1, "Bad tempo value") return } } } - if (parse.state != 3) { - if (parse.state == 1) { // tune header - info.Q = txt - glovar.tempo = s - return - } - goto_tune() - } - sym_link(s) - if (glovar.tempo && curvoice.time == 0) - glovar.tempo.invis = true + if (parse.state < 2) { // if in tune header + info.Q = txt + glovar.tempo = s + return + } + if (!curvoice.time) + glovar.tempo = s + else + sym_link(s) + if (!glovar.tempo) + syntax(0, "No previous tempo") } // treat the information fields which may embedded function do_info(info_type, text) { var s, d1, d2, a, vid @@ -1000,22 +1040,19 @@ // info fields in any state case 'I': self.do_pscom(text) break case 'L': -//fixme: ?? - if (parse.state == 2) - goto_tune(); a = text.match(/^1\/(\d+)(=(\d+)\/(\d+))?$/) if (a) { - d1 = Number(a[1]) + d1 = +a[1] if (!d1 || (d1 & (d1 - 1)) != 0) break d1 = C.BLEN / d1 if (a[2]) { // if '=' - d2 = Number(a[4]) - d2 = d2 ? Number(a[3]) / d2 * C.BLEN : 0 + d2 = +a[4] + d2 = d2 ? +a[3] / d2 * C.BLEN : 0 } else { d2 = d1 } } else if (text == "auto") { d1 = d2 = -1 @@ -1022,11 +1059,11 @@ } if (!d2) { syntax(1, "Bad L: value") break } - if (parse.state < 2) { + if (parse.state <= 1) { glovar.ulen = d1 } else { curvoice.ulen = d1; curvoice.dur_fact = d2 / d1 } @@ -1038,29 +1075,31 @@ set_user(text) break // fields in tune header or tune body case 'P': - if (parse.state == 0) + if (!parse.state) break if (parse.state == 1) { info.P = text break } - if (parse.state == 2) - goto_tune() + if (!parse.part) + parse.part = {} + if (parse.part[curvoice.time]) // P: already defined + break s = { - type: C.PART, text: text, - dur: 0 + time: curvoice.time } + set_ref(s) if (cfmt.writefields.indexOf(info_type) < 0) s.invis = true - sym_link(s) + parse.part[curvoice.time] = s break case 'Q': - if (parse.state == 0) + if (!parse.state) break new_tempo(text) break case 'V': get_voice(text) @@ -1120,11 +1159,11 @@ time = s2.time; auto_time = curvoice.time - time fac = curvoice.wmeasure / auto_time /* remove the invisible rest at start of tune */ - if (time == 0) { + if (!time) { while (s2 && !s2.dur) s2 = s2.next if (s2 && s2.type == C.REST && s2.invis) { time += s2.dur * fac @@ -1154,11 +1193,11 @@ } curvoice.time = s.time = time } /* -- parse a bar -- */ -function new_bar(dotted) { +function new_bar() { var s2, c, bar_type, line = parse.line, s = { type: C.BAR, fname: parse.fname, @@ -1165,12 +1204,10 @@ istart: parse.bol + line.index, dur: 0, multi: 0 // needed for decorations } - if (dotted) - s.bar_dotted = true if (vover && vover.bar) // end of voice overlay get_vover('|') if (glovar.new_nbar) { // %%setbarnb s.bar_num = glovar.new_nbar; glovar.new_nbar = 0 @@ -1187,27 +1224,23 @@ continue } break } if (bar_type[0] == ':') { - if (bar_type.length == 1) { // ":" alone + if (bar_type == ':') { // ":" alone bar_type = '|'; s.bar_dotted = true } else { s.rbstop = 2 // right repeat with end - if (curvoice.tie_s) - curvoice.tie_s.tie_s = s } } // set the annotations and the decorations if (a_gch) csan_add(s) - if (a_dcn) { - deco_cnv(a_dcn, s); - a_dcn = null - } + 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 @@ -1232,24 +1265,20 @@ && !(/[0-9" ]/.test(c))) { // " bar_type = bar_type.slice(0, -1); line.index--; c = '[' } - if (bar_type.slice(-1) == ':') // left repeat - s.rbstop = 2 // with bracket end - // check if repeat bar + // check if a repeat variant if (c > '0' && c <= '9') { s.text = c while (1) { c = line.next_char() if ("0123456789,.-".indexOf(c) < 0) break s.text += c } - s.rbstop = 2; - s.rbstart = 2 } else if (c == '"' && bar_type.slice(-1) == '[') { s.text = "" while (1) { c = line.next_char() if (!c) { @@ -1260,12 +1289,10 @@ line.index++ break } s.text += c } - s.rbstop = 2; - s.rbstart = 2 } // ']' as the first character indicates a repeat bar stop if (bar_type[0] == ']') { s.rbstop = 2 // with end @@ -1275,69 +1302,76 @@ s.invis = true } s.iend = parse.bol + line.index - if (s.rbstart - && curvoice.norepbra - && !curvoice.second) - s.norepbra = true + if (s.text + && bar_type.slice(-1) == '[' + && 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 + 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 + } // handle the accidentals (ties and repeat) if (s.text) { - if (bar_type.slice(-1) == '[' - && bar_type != '[') - bar_type = bar_type.slice(0, -1) + s.rbstop = 2; + s.rbstart = 2 if (s.text[0] == '1') { - if (curvoice.tie_s) - curvoice.tie_s_rep = curvoice.tie_s + curvoice.tie_s_rep = curvoice.tie_s if (curvoice.acc_tie) curvoice.acc_tie_rep = curvoice.acc_tie.slice() else if (curvoice.acc_tie_rep) curvoice.acc_tie_rep = null } else { - if (curvoice.tie_s_rep) { - curvoice.tie_s = clone(curvoice.tie_s_rep) - curvoice.tie_s.notes = clone(curvoice.tie_s.notes) - for (var m = 0; m <= curvoice.tie_s.nhd; m++) { - curvoice.tie_s.notes[m] = - clone(curvoice.tie_s.notes[m]) - curvoice.tie_s.notes[m].s = - curvoice.tie_s - } - } + curvoice.tie_s = curvoice.tie_s_rep if (curvoice.acc_tie_rep) curvoice.acc_tie = curvoice.acc_tie_rep.slice() } } + + if (s.rbstart + && curvoice.norepbra + && !curvoice.second) + s.norepbra = true + if (curvoice.ulen < 0) // L:auto adjust_dur(s); - if ((bar_type == "[" || bar_type == "|:") - && !curvoice.eoln) { + if (bar_type == "[" || bar_type == "|:") { 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) + 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) { // && !s2.a_gch && !s2.a_dd // && !s.a_gch && !s.a_dd) { // remove the invisible repeat bars // when no shift is needed - if (bar_type == "[" + if ((bar_type == "[" && !s2.text - && (curvoice.st == 0 + && (!curvoice.st || (par_sy.staves[curvoice.st - 1].flags & STOP_BAR) - || s.norepbra)) { + || s.norepbra)) + || s2.bar_type == "|") { // "|" + "|:" => "|:" + if (bar_type != "[") + s2.bar_type = bar_type if (s.text) s2.text = s.text if (s.a_gch) s2.a_gch = s.a_gch if (s.norepbra) @@ -1349,12 +1383,11 @@ //--fixme: pb when on next line and empty staff above return } // merge back-to-back repeat bars - if (s2.st == curvoice.st - && bar_type == "|:") { + if (bar_type == "|:") { if (s2.bar_type == ":|") { s2.bar_type = "::"; s2.rbstop = 2 return } @@ -1373,19 +1406,20 @@ case "[": s.rbstop = 2 case "[]": case "[|]": s.invis = true; - bar_type = "[]" + bar_type = s.rbstart ? "[" : "[]" break case ":|:": case ":||:": bar_type = "::" break case "||": - if (!cfmt.rbdbstop) + if (cfmt["abc-version"] >= "2.2") break + // fall thru - play repeat on double bar when old ABC version case "[|": case "|]": s.rbstop = 2 break } @@ -1399,13 +1433,14 @@ s.st = curvoice.st /* original staff */ /* if repeat bar and shift, add a repeat bar */ if (s.rbstart + && bar_type != "[" && !curvoice.norepbra - && curvoice.st > 0 - && !(par_sy.staves[curvoice.st - 1].flags & STOP_BAR)) { + && 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, @@ -1414,11 +1449,11 @@ invis: true, text: s.text, rbstart: 2 } sym_link(s2); - s2.st = curvoice.st + s2.st = s.st delete s.text; s.rbstart = 0 } if (!s.bar_dotted && !s.invis) @@ -1543,11 +1578,11 @@ } break } if (vids[vid]) { syntax(1, "Double voice in %%score") -// err = true + err = true } else { vids[vid] = true a_vf.push([vid, flags]) } flags = 0 @@ -1704,14 +1739,10 @@ } note.map = map = map[nn] if (map[1]) { // if print map note.pit = pit = map[1].pit note.acc = map[1].acc - if (!note.acc // if no accidental - && curvoice.acc[pit + 19]) // but one in the measure - note.acc = 3 - curvoice.acc[pit + 19] = note.acc == 3 ? 0 : note.acc } if (map[2]) // if color note.color = map[2] nn = map[3] if (nn) // if play map @@ -1739,12 +1770,14 @@ function parse_vpos() { var line = parse.line, ty = 0 - if (line.buffer[line.index - 1] == '.' && !a_dcn) + if (a_dcn.length && a_dcn[a_dcn.length - 1] == "dot") { ty = C.SL_DOTTED + a_dcn.pop() + } switch (line.next_char()) { case "'": line.index++ return ty + C.SL_ABOVE case ",": @@ -1825,11 +1858,11 @@ if (curvoice.snd_oct) o += curvoice.snd_oct if (acc == 3) // if natural accidental acc = 0 if (acc) { - if (typeof acc != "number") { + if (typeof acc == "object") { s = acc[0] / acc[1] // microtonal accidental if (acc[1] == 100) // in cents return p + o + s } else { s = acc // simple accidental @@ -1843,11 +1876,11 @@ if (!cfmt.temper) { p += o + s // standard temperament return p } } else { // equal temperament - if (typeof acc == "number") { // if not a fraction + if (typeof acc != "object") { // if not a fraction b40 = abc2svg.p_b40[pit % 7] + acc return cfmt.temper[b40] + o } if (acc[1] == cfmt.nedo) { // fraction of the edo divider @@ -1867,58 +1900,73 @@ p1 -= 12 s = -s } return p0 + o + (p1 - p0) * s } // pit2mid() + +// handle the ties +// @s = tie ending smbol +// @tei_s = tie starting symbol +function do_ties(s, tie_s) { + var i, m, not1, not2, mid, g, + nt = 0, + se = (tie_s.time + tie_s.dur) == curvoice.time // 'start-end' flag + + for (m = 0; m <= s.nhd; m++) { + not2 = s.notes[m] + mid = not2.midi + if (tie_s.type != C.GRACE) { + for (i = 0; i <= tie_s.nhd; i++) { + not1 = tie_s.notes[i] + if (!not1.tie_ty) + continue + if (not1.midi == mid + && (!se + || !not1.tie_e)) { // (if unison) + not2.tie_s = not1 + not2.s = s + if (se) { + not1.tie_e = not2 + not1.s = tie_s + } + nt++ + break + } + } + } else { + for (g = tie_s.extra; g; g = g.next) { + not1 = g.notes[0] // (fixme: only one note) + if (!not1.tie_ty) + continue + if (not1.midi == mid) { + g.ti1 = true + not2.tie_s = not1 + not2.s = s + not1.tie_e = not2 + not1.s = g + nt++ + break + } + } + } + } + + if (!nt) + error(1, tie_s, "Bad tie") + else + s.ti2 = true +} // do_ties() // (possible hook) Abc.prototype.new_note = function(grace, sls) { var note, s, in_chord, c, dcn, type, tie_s, acc_tie, - i, n, s2, nd, res, num, dur, apit, + i, n, s2, nd, res, num, dur, apit, div, ty, sl1 = [], line = parse.line, a_dcn_sav = a_dcn // save parsed decoration names - // handle the ties - function do_ties(s, tie_s) { - var m, note, mid - - for (m = 0; m <= s.nhd; m++) { - note = s.notes[m] - mid = note.midi - if (tie_s.type != C.GRACE) { - for (i = 0; i <= tie_s.nhd; i++) { - if (!tie_s.notes[i].tie_ty) - continue - // (tie_s.notes[i].tie_n may exist - // on repeat restart) - if (tie_s.notes[i].midi == mid) { - tie_s.notes[i].tie_n = note - note.s = s - tie_s.tie_s = s - break - } - } - } else { - for (s2 = tie_s.extra; s2; s2 = s2.next) { - if (!s2.notes[0].tie_ty) - continue - if (s2.notes[0].midi == mid) { - s2.tie_s = s - s2.notes[0].tie_n = note - note.s = s - s2.notes[0].s = s2 - tie_s.tie_s = s - break - } - } - } - s.ti2 = tie_s // pointer to the tie start - } - } // do_ties() - - a_dcn = null; + a_dcn = [] parse.stemless = false; s = { type: C.NOTE, fname: parse.fname, stem: 0, @@ -1929,17 +1977,17 @@ s.istart = parse.bol + line.index if (curvoice.color) s.color = curvoice.color - if (curvoice.tie_s) { // if tie from previous note / grace note - tie_s = curvoice.tie_s - curvoice.tie_s = null - } if (grace) { s.grace = true } else { + if (curvoice.tie_s) { // if tie from previous note / grace note + tie_s = curvoice.tie_s + curvoice.tie_s = null + } if (a_gch) csan_add(s) if (parse.repeat_n) { s.repeat_n = parse.repeat_n; s.repeat_k = parse.repeat_k; @@ -1956,18 +2004,20 @@ s.nmes = (c > '0' && c <= '9') ? line.get_int() : 1; s.dur = curvoice.wmeasure * s.nmes // ignore if in second voice if (curvoice.second) { + delete curvoice.eoln // ignore the end of line curvoice.time += s.dur return //null } // convert 'Z'/'Z1' to a whole measure rest if (s.nmes == 1) { s.type = C.REST; s.dur_orig = s.dur; + s.fmr = 1 // full measure rest s.notes = [{ pit: 18, dur: s.dur }] } else { @@ -1996,10 +2046,12 @@ nd = parse_dur(line); s.dur_orig = ((curvoice.ulen < 0) ? C.BLEN : curvoice.ulen) * nd[0] / nd[1]; s.dur = s.dur_orig * curvoice.dur_fact; + if (s.dur == curvoice.wmeasure) + s.fmr = 1 // full measure rest s.notes = [{ pit: 18, dur: s.dur_orig }] break @@ -2032,28 +2084,14 @@ case '(': sl1.push(parse_vpos()); c = line.char() continue case '!': - if (!a_dcn) - a_dcn = [] - if (type.length > 1) { + if (type.length > 1) a_dcn.push(type.slice(1, -1)) - } else { - dcn = "" - while (1) { - c = line.next_char() - if (!c) { - syntax(1, "No end of decoration") - return //null - } - if (c == '!') - break - dcn += c - } - a_dcn.push(dcn) - } + else + get_deco() // line -> a_dcn c = line.next_char() continue } break } @@ -2067,32 +2105,36 @@ return //null // transpose if (curvoice.octave) note.pit += curvoice.octave * 7 + apit = note.pit + 19 // pitch from C-1 - - // get the explicit or implicit accidental - // and keep the absolute pitch in (MIDI + cents) format i = note.acc + + // get the explicit or implicit accidental if (i) { curvoice.acc[apit] = i } else { i = curvoice.acc[apit] if (!i && acc_tie) i = acc_tie[apit] if (!i) i = curvoice.ckey.k_map[apit % 7] || 0 } - note.midi = pit2mid(apit, i) - if (curvoice.ckey.k_sndtran) - note.midi += abc2svg.b40m(curvoice.ckey.k_sndtran + - 122) - 36 + // map if (curvoice.map && maps[curvoice.map]) set_map(note, i) + + if (!note.midi) // if not map play + note.midi = pit2mid(apit, i) + + if (curvoice.ckey.k_sndtran) + note.midi += abc2svg.b40m(curvoice.ckey.k_sndtran + + 122) - 36 // starting slurs if (sl1.length) { while (1) { i = sl1.shift() @@ -2104,14 +2146,12 @@ ty: i }) } note.s = s; // link the note to the chord } - if (a_dcn) { - note.a_dcn = a_dcn; - a_dcn = null - } + if (a_dcn.length) + dh_cnv(s, note) s.notes.push(note) if (!in_chord) break // in chord: get the ending slurs and the ties @@ -2125,10 +2165,11 @@ continue case '-': note.tie_ty = parse_vpos() note.s = s curvoice.tie_s = s + s.ti1 = true if (curvoice.acc[apit] || (acc_tie && acc_tie[apit])) { if (!curvoice.acc_tie) curvoice.acc_tie = [] @@ -2141,10 +2182,11 @@ case '.': c = line.next_char() switch (c) { case '-': case '(': + a_dcn.push("dot") continue } syntax(1, "Misplaced dot") break } @@ -2187,18 +2229,14 @@ } if (s.grace && s.type != C.NOTE) { syntax(1, errs.bad_grace) return //null } - if (tie_s && s.type != C.NOTE) { - syntax(1, "Bad tie") - tie_s = null - } if (s.notes) { // if note or rest if (!grace) { - switch (curvoice.pos.stm) { + switch (curvoice.pos.stm & 0x07) { case C.SL_ABOVE: s.stem = 1; break case C.SL_BELOW: s.stem = -1; break case C.SL_HIDDEN: s.stemless = true; break } @@ -2236,12 +2274,11 @@ // adjust the time of the grace notes, bars... for (s2 = s2.next; s2; s2 = s2.next) s2.time = curvoice.time } } else { /* grace note - adjust its duration */ - var div = curvoice.ckey.k_bagpipe ? 8 : 4 - + div = curvoice.ckey.k_bagpipe ? 8 : 4 for (i = 0; i <= s.nhd; i++) s.notes[i].dur /= div; s.dur /= div; s.dur_orig /= div if (grace.stem) @@ -2252,20 +2289,24 @@ // get the possible ties and end of slurs c = line.char() while (1) { switch (c) { + case '.': + if (line.buffer[line.index + 1] != '-') + break + a_dcn.push("dot") + line.index++ + // fall thru case '-': - var ty = parse_vpos() + ty = parse_vpos() for (i = 0; i <= s.nhd; i++) { s.notes[i].tie_ty = ty s.notes[i].s = s } - if (grace) - grace.tie_s = curvoice.tie_s = grace - else - curvoice.tie_s = s + curvoice.tie_s = grace || s + curvoice.tie_s.ti1 = true for (i = 0; i <= s.nhd; i++) { note = s.notes[i] apit = note.pit + 19 // pitch from C-1 if (curvoice.acc[apit] || (acc_tie @@ -2277,20 +2318,10 @@ acc_tie[apit] } } c = line.char() continue - case ')': - s.notes[0].s = s - slur_add(s.notes[0]) - c = line.next_char() - continue - case '.': - if (line.buffer[line.index + 1] != '-') - break - c = line.next_char() - continue } break } // handle the ties ending on this chord/note @@ -2298,21 +2329,21 @@ do_ties(s, tie_s) } sym_link(s) - if (cfmt.shiftunison) - s.shiftunison = cfmt.shiftunison if (!grace) { if (!curvoice.lyric_restart) curvoice.lyric_restart = s if (!curvoice.sym_restart) curvoice.sym_restart = s } - if (a_dcn_sav) - deco_cnv(a_dcn_sav, s, s.prev) + 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 @@ -2347,10 +2378,36 @@ break } s = s.next } } // tp_adj() + +// get a decoration +function get_deco() { + var c, + line = parse.line, + i = line.index, // in case no deco end + dcn = "" + + while (1) { + c = line.next_char() + if (!c) { + syntax(1, "No end of decoration") + line.index = i; + return + } + if (c == '!') + break + dcn += c + } + if (ottava[dcn] != undefined) { + glovar.ottava = true; + parse.ottava.push(ottava[dcn]) + } else { + a_dcn.push(dcn) + } +} // get_deco() // characters in the music line (ASCII only) var nil = "0", char_tb = [ nil, nil, nil, nil, /* 00 - .. */ @@ -2406,14 +2463,14 @@ for (i = 1, j = line.index + 1; i < m.length; i++, j++) { if (m[i] == line.buffer[j]) continue if (m[i] != 'n') // search the base note - return //null + return //undefined b = ntb.indexOf(line.buffer[j]) if (b < 0) - return //null + return //undefined while (line.buffer[j + 1] == "'") { b += 7; j++ } while (line.buffer[j + 1] == ',') { @@ -2425,25 +2482,27 @@ return b } // check_mac() // convert a note as a number into a note as a ABC string function n2n(n) { - var c = ntb[n] + var c = '' while (n < 0) { n += 7; c += ',' } - while (n > 14) { + while (n >= 14) { n -= 7; c += "'" } - return c + return ntb[n] + c } // n2n() // expand a transposing macro function expand(m, b) { + if (b == undefined) // if static macro + return m var c, i, r = "", // result n = m.length for (i = 0; i < n; i++) { @@ -2493,21 +2552,21 @@ } curvoice.time = ti s = curvoice.last_sym parse.line = line = new scanBuf parse.istart += line_sav.index - line.buffer = b ? expand(m, b) : m + line.buffer = expand(m, b) parse_seq(true) if (curvoice.time != te) syntax(1, "Bad length of the macro sequence") if (!s) s = curvoice.sym for ( ; s; s = s.next) s.invis = s.play = true curvoice = curv } else { - line.buffer = b ? expand(m, b) : m; + line.buffer = expand(m, b) parse_seq(true) } parse.line = line = line_sav parse.istart = istart_sav @@ -2533,24 +2592,13 @@ if (!c) return } } - // special case for '.' (dot) - if (c == '.') { - switch (line.buffer[line.index + 1]) { - case '(': - case '-': - case '|': - c = line.next_char() - break - } - } - // check if start of a macro if (!in_mac && maci[c]) { - n = 0 + n = undefined for (k in mac) { if (!mac.hasOwnProperty(k) || k[0] != c) continue if (k.indexOf('n') < 0) { @@ -2558,11 +2606,11 @@ != line.index) continue line.index += k.length } else { n = check_mac(k) - if (!n) + if (n == undefined) continue } parse_mac(k, mac[k], n) n = 1 break @@ -2589,22 +2637,20 @@ } break case '\n': // line break if (cfmt.barsperstaff) break - if (par_sy.voices[curvoice.v] - && par_sy.voices[curvoice.v].range == 0) - curvoice.eoln = true + curvoice.eoln = true break case '&': // voice overlay if (grace) { syntax(1, errs.bad_grace) break } c = line.next_char() if (c == ')') { - get_vover(')') + get_vover(c) // full overlay stop break } get_vover('&') continue case '(': // slur start - tuplet - vover @@ -2639,16 +2685,31 @@ if (qplet == 0 || qplet == undefined) qplet = (curvoice.wmeasure % 9) == 0 ? 3 : 2; if (tpn < 0) tpn = tp.length // new tuplet + c = cfmt.tuplets + if (curvoice.pos.tup) { + c = Object.create(c) + switch (curvoice.pos.tup & 0x07) { + case C.SL_ABOVE: + c[3] = 1 + break + case C.SL_BELOW: + c[3] = 2 + break + case C.SL_HIDDEN: + c[2] = 1 + break + } + } tp.push({ p: pplet, q: qplet, r: rplet, ro: rplet, - f: cfmt.tuplets + f: c }) continue } if (c == '&') { // voice overlay start if (grace) { @@ -2690,37 +2751,14 @@ } s.notes[0].s = s slur_add(s.notes[0]) break case '!': // start of decoration - if (!a_dcn) - a_dcn = [] - if (type.length > 1) { // decoration letter - dcn = type.slice(1, -1) - } else { - dcn = ""; - i = line.index // in case no deco end - while (1) { - c = line.next_char() - if (!c) - break - if (c == '!') - break - dcn += c - } - if (!c) { - line.index = i; - syntax(1, "No end of decoration") - break - } - } - if (ottava[dcn] != undefined) { - glovar.ottava = true; - parse.ottava.push(ottava[dcn]) - } else { - a_dcn.push(dcn) - } + if (type.length > 1) // decoration letter + a_dcn.push(type.slice(1, -1)) + else + get_deco() // (line -> a_dcn) break case '"': if (grace) { syntax(1, errs.bad_grace) break @@ -2827,19 +2865,21 @@ break } last_note_sav = curvoice.last_note; curvoice.last_note = null; a_dcn_sav = a_dcn; - a_dcn = undefined; + a_dcn = [] grace = { type: C.GRACE, fname: parse.fname, istart: parse.bol + line.index, dur: 0, multi: 0 } - switch (curvoice.pos.gst) { + if (curvoice.color) + grace.color = curvoice.color + switch (curvoice.pos.gst & 0x07) { case C.SL_ABOVE: grace.stem = 1; break case C.SL_BELOW: grace.stem = -1; break case C.SL_HIDDEN: grace.stem = 2; break /* opposite */ } sym_link(grace); @@ -2852,19 +2892,19 @@ case '|': if (grace) { syntax(1, errs.bar_grace) break } - new_bar(line.buffer[line.index - 1] == '.') + new_bar() continue case '}': s = curvoice.last_note if (!grace || !s) { syntax(1, errs.bad_char, c) break } - if (a_dcn) + if (a_dcn.length) syntax(1, "Decoration ignored"); grace.extra = grace.next; grace.extra.prev = null; grace.next = null; curvoice.last_sym = grace; @@ -2891,15 +2931,12 @@ } line.index++ } } // parse_seq() - if (parse.state != 3) { // if not in tune body - if (parse.state != 2) - return - goto_tune() - } + if (parse.state != 3) // if not in tune body + return if (parse.tp) { tp = parse.tp tpn = parse.tpn tps = parse.tps @@ -2913,21 +2950,22 @@ parse.tps = tps parse.tpn = tpn } if (sls.length) syntax(1, "Start of slur without note") + if (a_dcn.length) { + syntax(1, "Decoration without symbol") + a_dcn = [] + } if (grace) { syntax(1, "No end of grace note sequence"); curvoice.last_sym = grace.prev; curvoice.last_note = last_note_sav if (grace.prev) grace.prev.next = null } - if (cfmt.breakoneoln && curvoice.last_note) - curvoice.last_note.beam_end = true - if (no_eol || cfmt.barsperstaff) - return - if (char_tb['\n'.charCodeAt(0)] == '\n' - && par_sy.voices[curvoice.v] - && par_sy.voices[curvoice.v].range == 0) + if (!no_eol && !cfmt.barsperstaff && !vover + && char_tb['\n'.charCodeAt(0)] == '\n') curvoice.eoln = true + if (curvoice.eoln && cfmt.breakoneoln && curvoice.last_note) + curvoice.last_note.beam_end = true } Index: core/subs.js ================================================================== --- core/subs.js +++ core/subs.js @@ -1,8 +1,8 @@ // abc2svg - subs.js - text output // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2021 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 @@ -20,10 +20,12 @@ // add font styles var sheet var add_fstyle = typeof document != "undefined" ? function(s) { var e + if (cfmt.fullsvg) + font_style += "\n" + s if (!sheet) { if (abc2svg.sheet) { // if styles from a previous generation sheet = abc2svg.sheet e = sheet.cssRules.length while (--e >= 0) @@ -133,16 +135,15 @@ el.innerHTML = str str.wh = [ el.clientWidth, el.clientHeight ] return str.wh } - str = str.replace(/<|>|&[^&]*?;|&| /g, function(c){ + str = str.replace(/<|>|&[^&\s]*?;|&/g, function(c){ switch (c) { case '<': return "<" case '>': return ">" case '&': return "&" - case " ": return '  ' // space + nbspace } return c // &xxx; }) while (1) { @@ -169,11 +170,10 @@ i0 = i } el.innerHTML = str.slice(i0); w += el.clientWidth; - gene.curfont = font return [w, h] } })() : // .. by internal tables @@ -202,20 +202,21 @@ swfac = font.swfac if (font.size > h) h = font.size continue case '&': + if (str[i + 1] == ' ') + break // normal '&' j = str.indexOf(';', i) if (j > 0 && j - i < 10) { i = j; c = 'a' // XML character reference } break } w += cwid(c) * swfac } - gene.curfont = font return [w, h] } // convert a string to a SVG text, handling the font changes // The string size is memorized into the String. @@ -242,16 +243,15 @@ cl = font_class(nf) return '' } // tspan() - o = str.replace(/<|>|&[^&]*?;|&| |\$./g, function(c){ + o = str.replace(/<|>|&[^&\s]*?;|&|\$./g, function(c){ switch (c) { case '<': return "<" case '>': return ">" case '&': return "&" - case ' ': return '  ' // space + nbspace default: if (c[0] != '$') break if (c[1] == '0') n_font = gene.deffont @@ -269,21 +269,22 @@ return c return c + tspan(c_font, o_font) } return c // &xxx; }) - if (c_font != o_font) { + if (c_font != o_font) o += "" - gene.curfont = c_font // keep current font for next paragraph - } // convert to String and memorize the string width and height o = new String(o) if (typeof document != "undefined") strwh(o) // browser else o.wh = strwh(str) // CLI + + gene.curfont = c_font // keep the current font for the next paragraph + return o } // str2svg() // set the default and current font function set_font(xxx) { @@ -345,11 +346,11 @@ if (cfmt.titletrim) { i = title.lastIndexOf(", ") if (i < 0 || title[i + 2] < 'A' || title[i + 2] > 'Z') { i = 0 - } else if (cfmt.titletrim == true) { // compatibility + } else if (cfmt.titletrim == 1) { // (true) compatibility if (i < title.length - 7 || title.indexOf(' ', i + 3) >= 0) i = 0 } else { if (i < title.length - cfmt.titletrim - 2) @@ -436,16 +437,10 @@ default: x = font.pad; break } j = 0 while (1) { i = text.indexOf('\n', j) - if (i < 0) { - str = str2svg(text.slice(j)) - vskip(str.wh[1] * cfmt.lineskipfac + font.pad * 2) - xy_str(x, font.pad, str, action) - break - } if (i == j) { // new paragraph vskip(parskip); blk_flush() use_font(gene.curfont) while (text[i + 1] == '\n') { @@ -453,13 +448,19 @@ i++ } if (i == text.length) break } else { - str = str2svg(text.slice(j, i)) - vskip(str.wh[1] * cfmt.lineskipfac + font.pad * 2) + if (i < 0) + str = text.slice(j) + else + str = text.slice(j, i) + vskip(strwh(str)[1] * cfmt.lineskipfac + + font.pad * 2) xy_str(x, font.pad, str, action) + if (i < 0) + break } j = i + 1 } vskip(parskip); blk_flush() @@ -472,28 +473,29 @@ if (i < 0) words = text.slice(j) else words = text.slice(j, i); words = words.split(/\s+/); - w = k = 0 - font = gene.curfont + w = k = wh = 0 for (j = 0; j < words.length; j++) { - ww = strwh(words[j])[0]; - w += ww + ww = strwh(words[j]) + w += ww[0] if (w >= strlw) { - str = str2svg(words.slice(k, j).join(' ')) - vskip(str.wh[1] * cfmt.lineskipfac) - xy_str(0, 0, str, action, strlw) + vskip(wh * cfmt.lineskipfac) + xy_str(0, 0, words.slice(k, j).join(' '), + action, strlw) k = j; - w = ww + w = ww[0] + wh = 0 } w += cwidf(' ') + if (ww[1] > wh) + wh = ww[1] } if (w != 0) { // last line - str = str2svg(words.slice(k).join(' ')) - vskip(str.wh[1] * cfmt.lineskipfac) - xy_str(0, 0, str) + vskip(wh * cfmt.lineskipfac) + xy_str(0, 0, words.slice(k).join(' ')) } vskip(parskip); blk_flush() if (i < 0) break @@ -516,16 +518,20 @@ maxn = 0, // max number of characters per line n = 1 // number of verses // output a line of words after tune function put_wline(p, x) { - var i = 0, j, k + var i = 0, + k = 0 - if (p[i] == '$' && p[i + 1] >= '0' && p[i + 1] <= '9') - i += 2; - k = 0; - j = i + if (p[0] == '$' // if font change + && p[1] >= '0' && p[1] <= '9') { + gene.curfont = p[1] == '0' ? gene.deffont + : get_font("u" + p[1]) + p = p.slice(2) + } + if ((p[i] >= '0' && p[i] <= '9') || p[i + 1] == '.') { while (i < p.length) { i++ if (p[i] == ' ' || p[i - 1] == ':' @@ -536,11 +542,11 @@ while (p[i] == ' ') i++ } if (k != 0) - xy_str(x, 0, p.slice(j, k), 'r') + xy_str(x, 0, p.slice(0, k), 'r') if (i < p.length) xy_str(x + 5, 0, p.slice(i), 'l') } // put_wline() set_font("words") @@ -679,11 +685,11 @@ X: "title" } function write_headform(lwidth) { var c, font, font_name, align, x, y, sz, info_val = {}, - info_font = clone(info_font_init), + info_font = Object.create(info_font_init), info_sz = { A: cfmt.infospace, C: cfmt.composerspace, O: cfmt.composerspace, R: cfmt.infospace Index: core/svg.js ================================================================== --- core/svg.js +++ core/svg.js @@ -1,8 +1,8 @@ // abc2svg - svg.js - svg functions // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2022 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -54,13 +54,15 @@ // glyphs in music font var tgls = { "mtr ": {x:0, y:0, c:"\u0020"}, // space brace: {x:0, y:0, c:"\ue000"}, - lphr: {x:0, y:24, c:"\ue030"}, - mphr: {x:0, y:24, c:"\ue038"}, - sphr: {x:0, y:27, c:"\ue039"}, + lphr: {x:0, y:23, c:"\ue030"}, + 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"}, @@ -71,10 +73,11 @@ spclef: {x:-6, y:0, c:"\ue069"}, stclef: {x:-8, y:0, c:"\ue07a"}, scclef: {x:-8, y:0, c:"\ue07b"}, sbclef: {x:-7, y:0, c:"\ue07c"}, oct: {x:0, y:2, c:"\ue07d"}, // 8 for clefs + oct2: {x:0, y:2, c:"\ue07e"}, // 15 for clefs mtr0: {x:0, y:0, c:"\ue080"}, // meters mtr1: {x:0, y:0, c:"\ue081"}, mtr2: {x:0, y:0, c:"\ue082"}, mtr3: {x:0, y:0, c:"\ue083"}, mtr4: {x:0, y:0, c:"\ue084"}, @@ -126,17 +129,18 @@ "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:0, c:"\ue4a0"}, + 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"}, - marcato: {x:-3, y:0, c:"\ue4ac"}, - hld: {x:-7, y:0, c:"\ue4c0"}, // fermata + marcato: {x:-3, y:-2, c:"\ue4ac"}, + 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"}, r2: {x:-3.2, y:0, c:"\ue4e4"}, r4: {x:-3, y:0, c:"\ue4e5"}, @@ -143,36 +147,36 @@ r8: {x:-3, y:0, c:"\ue4e6"}, r16: {x:-4, y:0, c:"\ue4e7"}, r32: {x:-4, y:0, c:"\ue4e8"}, r64: {x:-4, y:0, c:"\ue4e9"}, r128: {x:-4, y:0, c:"\ue4ea"}, - mrest: {x:-10, y:0, c:"\ue4ee"}, +// 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:-4, y:-6, c:"\ue522"}, - pppp: {x:-4, y:-6, c:"\ue529"}, - ppp: {x:-4, y:-6, c:"\ue52a"}, - pp: {x:-4, y:-6, c:"\ue52b"}, - mp: {x:-4, y:-6, c:"\ue52c"}, - mf: {x:-4, y:-6, c:"\ue52d"}, - ff: {x:-4, y:-6, c:"\ue52f"}, - fff: {x:-4, y:-6, c:"\ue530"}, - ffff: {x:-4, y:-6, c:"\ue531"}, - sfz: {x:-4, y:-6, c:"\ue539"}, - trl: {x:-4, y:-4, c:"\ue566"}, // trill - turn: {x:-5, y:-4, c:"\ue567"}, - turnx: {x:-5, y:-4, c:"\ue569"}, - umrd: {x:-7, y:-2, c:"\ue56c"}, - lmrd: {x:-7, y:-2, c:"\ue56d"}, - dplus: {x:-4, y:10, c:"\ue582"}, // plus + 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 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:0, c:"\ue618"}, + roll: {x:0, y:2, c:"\ue618"}, thumb: {x:0, y:0, c:"\ue624"}, snap: {x:-2, y:0, c:"\ue630"}, ped: {x:-10, y:0, c:"\ue650"}, pedoff: {x:-5, y:0, c:"\ue655"}, // "mtro.": {x:0, y:0, c:"\ue910"}, // (unused) @@ -197,11 +201,11 @@ return s.replace(/[Cco]\||[co]\.|./g, function(e) { var m = tgls["mtr" + e] //fixme: !! no m.x nor m.y yet !! // if (!m.x && !m.y) - return m.c + return m ? m.c : 0 // return '' + // m.c + '' }) @@ -428,12 +432,13 @@ function a_stop(s, t) { anno_out(s, t, user.anno_stop) } function empty_function() { } -var anno_start = user.anno_start ? a_start : empty_function, - anno_stop = user.anno_stop ? a_stop : empty_function + // the values are updated on generate() + var anno_start = empty_function, + anno_stop = empty_function // output the stop user annotations function anno_put() { var s while (1) { @@ -556,10 +561,55 @@ out_XYAB('\n' + } + } // hlud() + + for (st = 0; st <= nstaff; st++) { + p_st = staff_tb[st] + if (!p_st.hlu) + continue // (staff not yet displayed) + set_sscale(st) + hlud(p_st.hlu, 6) + hlud(p_st.hld, -6) + } + } // draw_all_hl() // output the list of glyphs and the stems // [0] = x glyph // [1] = y glyph // [2] = glyph code @@ -790,15 +840,16 @@ dx /= stv_g.scale; output += '\n' + 'v' + (-h).toFixed(1) + '"/>\n' } // tuplet bracket with number - the staves are not defined function out_tubrn(x, y, dx, dy, up, str) { - var sw = str.length * 10, + var dxx, + sw = str.length * 10, h = up ? -3 : 3; set_font("tuplet") xy_str(x + dx / 2, y + dy / 2 - gene.curfont.size * .5 + 2, str, 'c') @@ -805,15 +856,20 @@ dx /= stv_g.scale if (!up) y += 6; output += '\n' + '\n' } @@ -826,37 +882,37 @@ // decorations with string var deco_str_style = { crdc: { dx: 0, dy: 5, - style: 'font:italic 14px serif' + style: 'font:italic 14px text,serif' }, dacs: { dx: 0, dy: 3, - style: 'font:16px serif', - anchor: ' text-anchor="middle"' - }, -fng: { - dx: 0, - dy: 1, - style: 'font-family:Bookman; font-size:8px', + style: 'font:16px text,serif', anchor: ' text-anchor="middle"' }, pf: { dx: 0, dy: 5, - style: 'font:italic bold 16px serif' + style: 'font:italic bold 16px text,serif' }, '@': { dx: 0, dy: 5, - style: 'font: 12px sans-serif' + style: 'font:12px text,sans-serif' } } function out_deco_str(x, y, name, str) { + 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) @@ -884,23 +940,23 @@ x += 6 } g_close() } function out_cresc(x, y, val, defl) { - x += val; + 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' } @@ -918,70 +974,78 @@ xygl(x, y, "ped"); if (!defl.noen) xygl(x + val + 6, y, "pedoff") } function out_8va(x, y, val, defl) { + if (val < 18) { + val = 18 + x -= 4 + } if (!defl.nost) { out_XYAB('8\ +style="font:italic bold 12px text,serif">8\ va\n', x - 8, y); x += 12; val -= 12 - } else { - val -= 5 } y += 6; out_XYAB('\n', x, y, val) if (!defl.noen) out_XYAB('\n', x + val, y) } function out_8vb(x, y, val, defl) { + if (val < 18) { + val = 18 + x -= 4 + } if (!defl.nost) { out_XYAB('8\ -vb\n', +style="font:italic bold 12px text,serif">8\ +vb\n', x - 8, y); - x += 4; - val -= 4 - } else { - val -= 5 + x += 10 + val -= 10 } // y -= 2; out_XYAB('\n', x, y, val) if (!defl.noen) out_XYAB('\n', x + val, y) } function out_15ma(x, y, val, defl) { + if (val < 25) { + val = 25 + x -= 6 + } if (!defl.nost) { out_XYAB('15\ +style="font:italic bold 12px text,serif">15\ ma\n', x - 10, y); x += 20; val -= 20 - } else { - val -= 5 } y += 6; out_XYAB('\n', x, y, val) if (!defl.noen) out_XYAB('\n', x + val, y) } function out_15mb(x, y, val, defl) { + if (val < 24) { + val = 24 + x -= 5 + } if (!defl.nost) { out_XYAB('15\ -mb\n', +style="font:italic bold 12px text,serif">15\ +mb\n', x - 10, y); - x += 7; - val -= 7 - } else { - val -= 5 + x += 18 + val -= 18 } // y -= 2; out_XYAB('\n', x, y, val) if (!defl.noen) @@ -1106,11 +1170,11 @@ if (s.tempo_str1) { str.push(s.tempo_str1) w += strwh(s.tempo_str1)[0] } if (s.tempo_notes) { - dy = ' dy="-.05em"' // notes a bit higher + dy = ' dy="-1"' // notes a bit higher for (i = 0; i < s.tempo_notes.length; i++) { p = tempo_note(s, s.tempo_notes[i]) str.push('=') + str.push('=') w += cwidf('=') if (s.tempo_ca) { str.push(s.tempo_ca) w += strwh(s.tempo_ca)[0] j = s.tempo_ca.length + 1 @@ -1135,20 +1199,20 @@ p = tempo_note(s, s.new_beat) str.push('' + + 'px" dy="-1">' + p + '') j = p.length > 1 ? 2 : 1 w += j * gene.curfont.swfac dy = 'y' } } if (s.tempo_str2) { if (dy) - str.push('' + + str.push('' + s.tempo_str2 + '') else str.push(s.tempo_str2) w += strwh(s.tempo_str2)[0] } @@ -1155,36 +1219,33 @@ // build the string s.tempo_str = str.join(' ') w += cwidf(' ') * (str.length - 1) s.tempo_wh = [w, 13.0] // (the height is not used) - if (dy) - s.tempo_dy = dy } // tempo_build() // output a tempo function writempo(s, x, y) { - var bx + var bh set_font("tempo") if (gene.curfont.box) { gene.curfont.box = false - bx = x + bh = gene.curfont.size + 4 } //fixme: xy_str() cannot be used because in s.tempo_str //fixme: then there cannot be font changes by "$n" in the Q: texts output += '' + s.tempo_str + '\n' - if (bx) { + if (bh) { gene.curfont.box = true - bh = gene.curfont.size + 4; output += '\n' } @@ -1227,22 +1288,20 @@ if (user.imagesize) { head += user.imagesize + ' viewBox="0 0 ' + img.width.toFixed(0) + ' ' + posy.toFixed(0) + '">\n' } else { - head += ' width="' + img.width.toFixed(0) + + 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' + head += '\n' if (defs) head += '' + defs + '\n\n' // if %%pagescale != 1, do a global scale Index: core/tail.js ================================================================== --- core/tail.js +++ core/tail.js @@ -23,12 +23,19 @@ // Abc functions used by the modules Abc.prototype.a_de = function() { return a_de } Abc.prototype.add_style = function(s) { style += s }; Abc.prototype.cfmt = function() { return cfmt }; Abc.prototype.clone = clone; -Abc.prototype.deco_cnv = deco_cnv; +Abc.prototype.deco_put = function(nm, s) { + a_dcn.push(nm) + deco_cnv(s) +} Abc.prototype.defs_add = defs_add +Abc.prototype.dh_put = function(nm, s, nt) { + a_dcn.push(nm) + dh_cnv(s, nt) +} Abc.prototype.draw_meter = draw_meter Abc.prototype.draw_note = draw_note; Abc.prototype.errs = errs; Abc.prototype.font_class = font_class; Abc.prototype.gch_tr1 = gch_tr1; @@ -54,11 +61,10 @@ Abc.prototype.get_top_v = function() { return par_sy.top_voice }; Abc.prototype.get_tsfirst = function() { return tsfirst }; Abc.prototype.get_unit = get_unit; Abc.prototype.get_voice_tb = function() { return voice_tb }; Abc.prototype.glout = glout -Abc.prototype.goto_tune = goto_tune; Abc.prototype.info = function() { return info }; Abc.prototype.new_block = new_block; Abc.prototype.out_arp = out_arp; Abc.prototype.out_deco_str = out_deco_str; Abc.prototype.out_deco_val = out_deco_val; Index: core/tune.js ================================================================== --- core/tune.js +++ core/tune.js @@ -1,8 +1,8 @@ // abc2svg - tune.js - tune generation // -// Copyright (C) 2014-2020 Jean-Francois Moine +// Copyright (C) 2014-2022 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -55,33 +55,43 @@ } } /* -- link a ABC symbol into the current voice -- */ function sym_link(s) { + var tim = curvoice.time + if (!s.fname) set_ref(s) - parse.last_sym = s; - s.prev = curvoice.last_sym - if (curvoice.last_sym) - curvoice.last_sym.next = s - else - curvoice.sym = s; - curvoice.last_sym = s + parse.last_sym = s + s.prev = curvoice.last_sym + if (curvoice.last_sym) + curvoice.last_sym.next = s + else + curvoice.sym = s + curvoice.last_sym = s s.v = curvoice.v; s.p_v = curvoice; s.st = curvoice.cst; - s.time = curvoice.time + s.time = tim if (s.dur && !s.grace) curvoice.time += s.dur; + parse.ufmt = true + s.fmt = cfmt // global parameters s.pos = curvoice.pos if (curvoice.second) s.second = true if (curvoice.floating) s.floating = true if (curvoice.eoln) { s.soln = true curvoice.eoln = false + } + if (parse.part && parse.part[tim]) { + if (!parse.part[tim].done) { + s.part = parse.part[tim] + parse.part[tim].done = true + } } } /* -- add a new symbol in a voice -- */ function sym_add(p_voice, type) { @@ -107,81 +117,109 @@ } /* -- sort all symbols by time and vertical sequence -- */ // weight of the symbols !! depends on the symbol type !! var w_tb = new Uint8Array([ - 4, // bar + 5, // bar 1, // clef 8, // custos 0, // (free) - 3, // grace - 5, // key - 6, // meter + 4, // grace + 2, // key + 3, // meter 9, // mrest 9, // note 0, // part 9, // rest - 3, // space + 4, // space 0, // staves 7, // stbrk 0, // tempo 0, // (free) 0, // block 0 // remark ]) function sort_all() { - var s, s2, p_voice, v, time, w, wmin, ir, multi, - prev, nb, ir2, v2, fl, new_sy, + var s, s2, time, w, wmin, ir, fmt, + fl, new_sy, nv = voice_tb.length, vtb = [], vn = [], // voice indexed by range - sy = cur_sy - - for (v = 0; v < nv; v++) - vtb.push(voice_tb[v].sym) - - // set the first symbol - ir2 = nv - multi = -1 + 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 + + prev.fmt = fmt = p_voice.sym.fmt // starting format + + // set the first symbol of each voice for (v = 0; v < nv; v++) { - if (!sy.voices[v]) - continue - ir = sy.voices[v].range - if (ir < ir2) - ir2 = ir - vn[ir] = v - multi++ - } - v = vn[ir2] - tsfirst = prev = vtb[v] - if (!tsfirst) - return // no music - vtb[v] = tsfirst.next - prev.seqst = true - fl = !w_tb[prev.type] || tsfirst.type == tsfirst.next + s = voice_tb[v].sym + vtb[v] = s + if (sy.voices[v]) + vn[sy.voices[v].range] = v + } + + // insert the first staff system in the top voice + p_voice.sym = tsfirst = s = prev + if (s.next) + s.next.prev = s + else + p_voice.last_sym = s + + // if Q: from tune header, put it at start of the music + // (after the staff system) + s = glovar.tempo + if (s) { + s.v = v = p_voice.v + s.p_v = p_voice + s.st = 0 + s.time = 0 + s.prev = prev + s.next = prev.next + if (s.next) + s.next.prev = s + else + p_voice.last_sym = s + s.prev.next = s + s.fmt = fmt + glovar.tempo = null + vtb[v] = s + } // loop on the symbols of all voices while (1) { - if (new_sy && fl) { + if (new_sy) { sy = new_sy; new_sy = null; - multi = -1; vn = [] for (v = 0; v < nv; v++) { if (!sy.voices[v]) continue - ir = sy.voices[v].range - vn[ir] = v; - multi++ + vn[sy.voices[v].range] = v } } /* search the min time and symbol weight */ - wmin = time = 1000000 /* big int */ - for (ir = 0; ir < nv; ir++) { - v = vn[ir] + wmin = time = 10000000 // big int + ir = 0 + while (1) { + v = vn[ir++] if (v == undefined) break s = vtb[v] if (!s || s.time > time) continue @@ -196,34 +234,21 @@ if (wmin > 127) break // done /* link the vertical sequence */ - for (ir = 0; ir < nv; ir++) { - v = vn[ir] + ir = 0 + while (1) { + v = vn[ir++] if (v == undefined) break s = vtb[v] if (!s || s.time != time || w_tb[s.type] != wmin) continue - if (s.type == C.STAVES) { - new_sy = s.sy; - - // set all voices of previous and next staff systems - // as reachable - for (ir2 = 0; ir2 < nv; ir2++) { - if (vn[ir2] == undefined) - break - } - for (v2 = 0; v2 < nv; v2++) { - if (!new_sy.voices[v2] - || sy.voices[v2]) - continue - vn[ir2++] = v2 - } - } + if (s.type == C.STAVES) + new_sy = s.sy if (fl) { fl = 0; s.seqst = true } s.ts_prev = prev @@ -236,11 +261,11 @@ } } // adjust some voice elements function voice_adj(sys_chg) { - var p_voice, s, s2, v + var p_voice, s, s2, v, sl // 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, @@ -287,41 +312,16 @@ if (curvoice && curvoice.clone) { parse.istart = parse.eol do_cloning() } - // if Q: from tune header, put it at start of the music - // (after the staff system) - s = glovar.tempo - if (s && staves_found <= 0) { // && !s.invis) { - play problem - //fixme: which play problem? - v = par_sy.top_voice; - p_voice = voice_tb[v]; - if (p_voice.sym - && p_voice.sym.type != C.TEMPO - && (!p_voice.sym.next - || p_voice.sym.next.type != C.TEMPO)) { - s = clone(s); - s.v = v; - s.p_v = p_voice; - s.st = p_voice.st; - s.time = 0; - s.prev = p_voice.sym - s.next = p_voice.sym.next - if (s.next) - s.next.prev = s - p_voice.sym.next = s - } - } - 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 - var sl = p_voice.sls.shift() - + sl = p_voice.sls.shift() if (!sl) break s = sl.note.s for (s2 = s.next; s2; s2 = s2.next) { if (s2.bar_type && s2.bar_type[0] == ':') @@ -333,11 +333,11 @@ s.sls.push({ ty: sl.ty, note: {s: s2} }) } else { - syntax(1, "Lack of ending slur(s)") + error(1, s, "Lack of ending slur(s)") } } } // not %%score for (s = p_voice.sym; s; s = s.next) { if (s.time >= staves_found) @@ -344,11 +344,11 @@ break } for ( ; s; s = s.next) { switch (s.type) { case C.GRACE: - // with w_tb[C.BAR] = 2, + // with w_tb[C.BAR] = 4 and w_tb[C.GRACE] = 3, // the grace notes go after the bar; // if before a bar, change the grace time if (s.next && s.next.type == C.BAR) s.time-- @@ -365,24 +365,10 @@ s2.a_ly = null break } break } - continue - case C.MREST: - if (s.next && s.next.type != C.BAR) { - syntax(1, "Lack of bar after multi-measure rest") - s2 = clone(s) - s2.type = C.BAR - s2.bar_type = '|' - s2.dur = 0 - s2.time = s.next.time - s2.prev = s - s2.next = s.next - s2.next.prev = s2 - s.next = s2 - } continue } if (s.feathered_beam) set_feathered_beam(s) @@ -390,16 +376,16 @@ } } /* -- create a new staff system -- */ function new_syst(init) { - var st, v, - sy_new = { - voices: [], - staves: [], - top_voice: 0 - } + var st, v, sy_staff, p_voice, + sy_new = { + voices: [], + staves: [], + top_voice: 0 + } if (init) { /* first staff system */ cur_sy = par_sy = sy_new return } @@ -406,12 +392,12 @@ // update the previous system for (v = 0; v < voice_tb.length; v++) { if (par_sy.voices[v]) { st = par_sy.voices[v].st - var sy_staff = par_sy.staves[st], - p_voice = voice_tb[v] + sy_staff = par_sy.staves[st] + p_voice = voice_tb[v] sy_staff.staffnonote = p_voice.staffnonote if (p_voice.staffscale) sy_staff.staffscale = p_voice.staffscale; } @@ -468,10 +454,11 @@ for ( ; s; s = s.ts_next) { if (!s.seqst) continue switch (s.type) { case C.METER: + if (wmeasure != 1) // if not M:none bar_num += (s.time - bar_tim) / wmeasure bar_tim = s.time wmeasure = s.wmeasure break case C.BAR: @@ -543,11 +530,11 @@ function not2abc(pit, acc) { var i, nn = '' if (acc && acc != 3) { - if (typeof acc == "number") { + if (typeof acc != "object") { nn = ['__', '_', '', '^', '^^'][acc + 2] } else { i = acc[0] if (i > 0) { nn += '^' @@ -557,13 +544,13 @@ } nn += i + '/' + acc[1] } } nn += ntb[(pit + 75) % 7] - for (i = pit; i >= 21; i -= 7) + for (i = pit; i >= 23; i -= 7) nn += "'" - for (i = pit; i < 14; i += 7) + for (i = pit; i < 16; i += 7) nn += "," return nn } // not2abc() // note mapping @@ -692,10 +679,11 @@ } else { curvoice.vtransp = transp } if (is_voice_sig()) { // if no symbol yet + curvoice.okey.fmt = cfmt curvoice.key = s = clone(curvoice.okey) } else { s = curvoice.last_sym while (1) { // set the transposition in the previous K: if (s.type == C.KEY) @@ -720,10 +708,18 @@ function note_transp(s, sk, note) { var ak, an, d, b40, n = note.pit, a = note.acc + if (typeof a == "object") { + d = a[1] + a = a[0] + if (d != 2) { + error(1, s, "Microtone transposition not coded") + return + } + } if (!a && sk.k_a_acc) // if accidental list a = sk.k_map[(n + 19) % 7] // invisible accidental b40 = abc2svg.pab40(n, a) + sk.k_transp // base-40 transposition @@ -754,11 +750,63 @@ if (ak) an = 3 // natural } else { return // same accidental (in the key) } - note.acc = an + if (d && an != a) { + switch (Number(a)) { + case -3: + switch (an) { + case -2: + an = -1 + break + case 3: // natural + an = -3 + note.pit++ + break + case 2: + an = -1 + break + } + break + case -1: + switch (an) { + case -2: + an = -3 + break + case 3: // natural + an = 1 + break + } + break + case 1: + switch (an) { + case -1: + an = -3 + break + case 3: // natural + an = -1 + break + case 2: + an = 3 + break + } + break + case 3: + switch (an) { + case -1: + an = 1 + break + case 1: + an = 1 + note.pit++ + break + } + break + } + } + note.acc = d ? [an, d] : an } // adjust the pitches according to the transposition(s) function pit_adj() { var i, p_v, s, sk, g, @@ -769,10 +817,11 @@ if (p_v.vtransp == undefined) continue // no transposition in this voice if (p_v.key.k_transp) { sk = p_v.key key_transp(sk) + sk.k_old_sf = sk.k_sf // no natural } else { sk = null } s = p_v.sym while (s) { @@ -868,29 +917,13 @@ param = param.slice(0, -5).trim() } else if (fmt_lock[cmd]) { return } - if (cmd.slice(0, 5) == "title" && parse.state == 2) - goto_tune() - switch (cmd) { - case "center": - set_font("text") - if (parse.state >= 2) { - s = new_block("text"); - s.text = param - s.opt = 'c' - s.font = cfmt.textfont - return - } - write_text(param, 'c') - return case "clef": if (parse.state >= 2) { - if (parse.state == 2) - goto_tune(); s = new_clef(param) if (s) get_clef(s) } return @@ -902,24 +935,24 @@ return case "map": get_map(param) return case "maxsysstaffsep": + case "sysstaffsep": if (parse.state == 3) { val = get_unit(param) if (isNaN(val)) { - syntax(1, errs.bad_val, "%%maxsysstaffsep") + syntax(1, errs.bad_val, "%%" + cmd) return } - par_sy.voices[curvoice.v].maxsep = val + par_sy.voices[curvoice.v][cmd[0] == 'm' ? "maxsep" : "sep"] = + val return } break case "multicol": if (parse.state >= 2) { - if (parse.state == 2) - goto_tune() curvoice = voice_tb[0] s = new_block("mc_" + param) break } switch (param) { @@ -961,29 +994,23 @@ syntax(1, "Unknown keyword '$1' in %%multicol", param) break } return case "ottava": - if (parse.state != 3) { - if (parse.state != 2) - return - goto_tune() - } + if (parse.state != 3) + return n = parseInt(param) if (isNaN(n) || n < -2 || n > 2) { syntax(1, errs.bad_val, "%%ottava") return } glovar.ottava = true; parse.ottava.push(n) return case "repbra": - if (parse.state >= 2) { - if (parse.state == 2) - goto_tune(); + if (curvoice) curvoice.norepbra = !get_bool(param) - } return case "repeat": if (parse.state != 3) return if (!curvoice.last_sym) { @@ -1059,20 +1086,15 @@ val = parseInt(param) if (isNaN(val) || val < 1) { syntax(1, "Bad %%setbarnb value") break } - if (parse.state == 2) - goto_tune() glovar.new_nbar = val return case "staff": - if (parse.state != 3) { - if (parse.state != 2) - return - goto_tune() - } + if (parse.state != 3) + return val = parseInt(param) if (isNaN(val)) { syntax(1, "Bad %%staff value '$1'", param) return } @@ -1088,15 +1110,12 @@ } delete curvoice.floating; curvoice.cst = st return case "staffbreak": - if (parse.state != 3) { - if (parse.state != 2) - return - goto_tune() - } + if (parse.state != 3) + return s = { type: C.STBRK, dur:0 } if (param.slice(-1) == 'f') { @@ -1120,60 +1139,47 @@ case "staffnonote": set_v_param(cmd, param) return case "staves": case "score": - if (parse.state == 0) + if (!parse.state) return if (parse.scores && parse.scores.length > 0) { text = parse.scores.shift(); cmd = text.match(/([^\s]+)\s*(.*)/); - get_staves(cmd[1], cmd[2]) - } else { - get_staves(cmd, param) - } - return - case "sysstaffsep": -//--fixme: may be global - if (parse.state == 3) { - val = get_unit(param) - if (isNaN(val)) { - syntax(1, errs.bad_val, "%%sysstaffsep") - return - } - par_sy.voices[curvoice.v].sep = val - return - } - break + param = cmd[2] + cmd = cmd[1] + } + get_staves(cmd, param) + return + case "center": case "text": + k = cmd[0] == 'c' ? 'c' : cfmt.textoption set_font("text") if (parse.state >= 2) { - s = new_block(cmd); + s = new_block("text") s.text = param - s.opt = cfmt.textoption + s.opt = k s.font = cfmt.textfont return } - write_text(param, cfmt.textoption) + write_text(param, k) return case "transpose": // (abcm2ps compatibility) if (cfmt.sound) return val = get_transp(param) if (val == undefined) { // accept note interval val = get_interval(param) - if (val == undefined) { - syntax(1, errs.bad_transp) + if (val == undefined) return - } } switch (parse.state) { case 0: cfmt.transp = 0 // fall thru case 1: - case 2: cfmt.transp = (cfmt.transp || 0) + val return } for (s = curvoice.last_sym; s; s = s.prev) { switch (s.type) { @@ -1197,16 +1203,12 @@ return case "user": set_user(param) return case "voicecolor": - if (parse.state != 3) { - if (parse.state != 2) - return - goto_tune() - } - curvoice.color = param + if (curvoice) + curvoice.color = param return case "vskip": val = get_unit(param) if (isNaN(val)) { syntax(1, errs.bad_val, "%%vskip") @@ -1257,10 +1259,14 @@ switch (type) { case "js": js_inject(text) break case "ml": + if (cfmt.pageheight) { + syntax(1, "Cannot have %%beginml with %%pageheight") + break + } if (parse.state >= 2) { s = new_block(type); s.text = text } else { blk_flush() @@ -1298,11 +1304,13 @@ case "text": action = get_textopt(opt); if (!action) action = cfmt.textoption set_font("text") - if (parse.state >= 2) { + if (text.indexOf('\\') >= 0) + text = cnv_escape(text) + if (parse.state > 1) { s = new_block(type); s.text = text s.opt = action s.font = cfmt.textfont break @@ -1327,16 +1335,18 @@ if (vover) { syntax(1, "No end of voice overlay"); get_vover(vover.bar ? '|' : ')') } - if (!voice_tb.length) - return voice_adj(); sort_all() /* define the time / vertical sequences */ if (!tsfirst) return + if (user.anno_start) + anno_start = a_start + if (user.anno_stop) + anno_stop = a_stop self.set_bar_num() pit_adj() if (info.P) tsfirst.parts = info.P // for play @@ -1437,37 +1447,34 @@ return //undefined } /* -- get staves definition (%%staves / %%score) -- */ function get_staves(cmd, parm) { - var s, p_voice, p_voice2, i, flags, v, vid, - st, range, - a_vf = parse_staves(parm) // array of [vid, flags] - - if (!a_vf) - return - - if (voice_tb.length) - voice_adj(true); + var s, p_voice, p_voice2, i, flags, v, vid, a_vf, + st, range, + nv = voice_tb.length, + maxtime = 0 + + if (parm) { + a_vf = parse_staves(parm) // => array of [vid, flags] + if (!a_vf) + return + } /* create a new staff system */ - var maxtime = 0, - no_sym = true - - for (v = 0; v < voice_tb.length; v++) { + for (v = 0; v < nv; v++) { p_voice = voice_tb[v] if (p_voice.time > maxtime) maxtime = p_voice.time - if (p_voice.sym) - no_sym = false } - if (no_sym /* if first %%staves */ - || (maxtime == 0 && staves_found < 0)) { + if (!maxtime) { // if first %%staves par_sy.staves = [] par_sy.voices = [] } else { + if (nv) // if many voices + voice_adj(true) /* * create a new staff system and * link the 'staves' symbol in a voice which is seen from * the previous system - see sort_all */ @@ -1483,18 +1490,32 @@ dur: 0 } sym_link(s); // link the staves in this voice par_sy.nstaff = nstaff; + + // if no parameter, duplicate the current staff system + // and do a voice re-synchronization + if (!parm) { + s.sy = clone(par_sy, 1) + par_sy.next = s.sy + par_sy = s.sy + staves_found = maxtime + for (v = 0; v < nv; v++) + voice_tb[v].time = maxtime + curvoice = voice_tb[par_sy.top_voice] + return + } + new_syst(); s.sy = par_sy } staves_found = maxtime /* initialize the (old) voices */ - for (v = 0; v < voice_tb.length; v++) { + for (v = 0; v < nv; v++) { p_voice = voice_tb[v] delete p_voice.second delete p_voice.ignore delete p_voice.floating } @@ -1507,12 +1528,13 @@ a_vf[i][0] = p_voice; // set the range and add the overlay voices while (1) { - par_sy.voices[v] = {} - par_sy.voices[v].range = range++ + par_sy.voices[v] = { + range: range++ + } p_voice = p_voice.voice_down if (!p_voice) break v = p_voice.v } @@ -1607,12 +1629,26 @@ if (cmd[1] == 'c') { /* if %%score */ for (st = 0; st < nstaff; st++) par_sy.staves[st].flags ^= STOP_BAR } - for (v = 0; v < voice_tb.length; v++) { + nv = voice_tb.length + st = 0 + for (v = 0; v < nv; v++) { p_voice = voice_tb[v] + if (par_sy.voices[v]) + st = p_voice.st + else + p_voice.st = st // (this avoids later crashes) + + // if first %%staves + // update the staff of the symbols with no time + if (!maxtime) { + for (s = p_voice.sym; s; s = s.next) + s.st = st + } + if (!par_sy.voices[v]) continue // set the staff of the overlay voices p_voice2 = p_voice.voice_down @@ -1627,10 +1663,12 @@ st = p_voice.st if (st > 0 && !p_voice.norepbra && !(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 @@ -1653,11 +1691,10 @@ delete p_voice.nm delete p_voice.snm delete p_voice.new_name delete p_voice.lyric_restart delete p_voice.lyric_cont - delete p_voice.ly_a_h; delete p_voice.sym_restart delete p_voice.sym_cont delete p_voice.have_ly delete p_voice.tie_s @@ -1735,21 +1772,21 @@ p_voice2.ulen = curvoice.ulen p_voice2.dur_fact = curvoice.dur_fact p_voice2.acc = [] // no accidental if (!vover) { /* first '&' in a measure */ - vover = { - bar: true, - p_voice: curvoice - } time = p_voice2.time for (s = curvoice.last_sym; /*s*/; s = s.prev) { if (s.type == C.BAR || s.time <= time) /* (if start of tune) */ break } - vover.time = s.time + vover = { + bar: (s && s.bar_type) ? s.bar_type : '|', + p_voice: curvoice, + time: s.time + } } else { if (curvoice != vover.p_voice && curvoice.time != vover.p_voice.time) { syntax(1, "Wrong duration in voice overlay") if (curvoice.time > vover.p_voice.time) @@ -1759,14 +1796,15 @@ p_voice2.time = vover.time; curvoice = p_voice2 // add a bar at start of the measure overlay // (needed for sort_all() in case of spaces - 'y') - if (vover.bar) { + if (vover.bar && vover.time) { sym_link({ type: C.BAR, - bar_type: type, + bar_type: vover.bar, + invis: 1, dur: 0, multi: 0 }) } } @@ -1773,24 +1811,25 @@ // check if a clef, key or time signature may go at start of the current voice function is_voice_sig() { var s -// if (!curvoice.sym) -// return true // new voice (may appear in the middle of a tune) - if (curvoice.time != 0) + if (!curvoice.last_sym) + return true + if (curvoice.time) return false for (s = curvoice.last_sym; s; s = s.prev) - if (w_tb[s.type] != 0) + if (w_tb[s.type]) return false return true } // treat a clef found in the tune body function get_clef(s) { if (is_voice_sig()) { curvoice.clef = s + s.fmt = cfmt return } // clef change sym_link(s); @@ -1821,12 +1860,11 @@ a = new_key(parm), s_key = a[0]; a = a[1] - switch (parse.state) { - case 1: // in tune header (first K:) + if (parse.state == 1) { // in tune header (first K:) if (s_key.k_sf == undefined && !s_key.k_a_acc) { // empty K: s_key.k_sf = 0; s_key.k_none = true s_key.k_map = abc2svg.keys[7] } @@ -1835,22 +1873,21 @@ 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) if (!glovar.ulen) glovar.ulen = C.BLEN / 8; - parse.state = 2; // in tune header after K: - return - case 2: // K: at start of tune body - goto_tune(true) - break - } - if (a.length) - set_kv_parm(a) + if (a.length) { + memo_kv_parm('*', a) + a = '' + } + goto_tune() + parse.state = 3 // in tune body + } + + set_kv_parm(a) if (!curvoice.ckey.k_bagpipe && !curvoice.ckey.k_drum && (cfmt.transp != undefined || curvoice.transp != undefined || curvoice.shift != undefined)) @@ -1865,11 +1902,11 @@ if (s_key.k_sf == undefined) { if (!s_key.k_a_acc && transp == undefined) { if (sndtran == undefined) return // not a key signature - s_key.k_play = true // play only + s_key.invis = true // play only } s_key.k_sf = curvoice.okey.k_sf } curvoice.okey = clone(s_key) @@ -1879,16 +1916,22 @@ } if (sndtran != undefined) s_key.k_sndtran = sndtran 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) @@ -1895,20 +1938,21 @@ } } // get / create a new voice function new_voice(id) { - var p_voice, v, p_v_sav, - n = voice_tb.length + var v, p_v_sav, + p_voice = voice_tb[0], + n = voice_tb.length // if first explicit voice and no music, replace the default V:1 if (n == 1 - && voice_tb[0].default) { - delete voice_tb[0].default - if (voice_tb[0].time == 0) { - p_voice = voice_tb[0]; + && 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; set_transp(); @@ -1927,17 +1971,18 @@ v: v, id: id, time: 0, new: true, pos: { - dyn: 0, - gch: 0, - gst: 0, - orn: 0, - stm: 0, - voc: 0, - vol: 0 +// dyn: 0, +// gch: 0, +// gst: 0, +// orn: 0, +// stm: 0, +// tup: 0, +// voc: 0, +// vol: 0 }, scale: 1, // st: 0, // cst: 0, ulen: glovar.ulen, @@ -1975,10 +2020,11 @@ curvoice = null; new_syst(true); staves_found = -1; gene = {} a_de = [] // remove old decorations + cross = {} // new cross voice decorations } // treat V: with many voices function do_cloning() { var i, @@ -2043,11 +2089,11 @@ return } curvoice = new_voice(vs[0]) - // if many voice, memorize the start of sequence + // if many voices, memorize the start of sequence if (vs.length > 1) { vs.shift() curvoice.clone = { vs: vs, a: a, @@ -2056,13 +2102,10 @@ if (parse.file[curvoice.clone.bol - 1] != ']') curvoice.clone.bol++ // start of new line } set_kv_parm(a) - if (parse.state == 2) // if first voice - goto_tune(); - set_transp(); v = curvoice.v if (curvoice.new) { // if new voice delete curvoice.new if (staves_found < 0) { // if no %%score/%%staves @@ -2086,63 +2129,53 @@ curvoice.filtered = true; voice_filter() } } -// change state from 'tune header after K:' to 'in tune body' +// change state from 'tune header' to 'in tune body' // curvoice is defined when called from get_voice() -function goto_tune(is_K) { - var v, p_voice, - s = { - type: C.STAVES, - dur: 0, - sy: par_sy - } +function goto_tune() { + var v, p_voice set_page(); write_heading(); - reset_gen(); if (glovar.new_nbar) { gene.nbar = glovar.new_nbar // measure numbering glovar.new_nbar = 0 } else { gene.nbar = 1 } - parse.state = 3; // in tune body - // if no voice yet, create the default voice if (!voice_tb.length) { get_voice("1"); curvoice.clef.istart = curvoice.key.istart; curvoice.clef.iend = curvoice.key.iend; // nstaff = 0; curvoice.default = true - } else if (!curvoice) { + } else { curvoice = voice_tb[staves_found < 0 ? 0 : par_sy.top_voice] } - if (!curvoice.init && !is_K) { - set_kv_parm([]) - set_transp() - } - // 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 && !p_voice.pos.stm) { - p_voice.pos = clone(p_voice.pos); - p_voice.pos.stm = C.SL_BELOW + p_voice.pos = clone(p_voice.pos) + p_voice.pos.stm &= ~0x07 + p_voice.pos.stm |= C.SL_BELOW } } // initialize the voices when no %%staves/score if (staves_found < 0) { - nstaff = voice_tb.length - 1 - for (v = 0; v <= nstaff; v++) { + v = voice_tb.length + par_sy.nstaff = + nstaff = v - 1 + while (--v >= 0) { p_voice = voice_tb[v]; delete p_voice.new; // old voice p_voice.st = p_voice.cst = v; par_sy.voices[v] = { st: v, @@ -2151,14 +2184,7 @@ par_sy.staves[v] = { stafflines: p_voice.stafflines || "|||||", staffscale: 1 } } - par_sy.nstaff = nstaff - } - - // link the first %%score in the top voice - p_voice = curvoice; - curvoice = voice_tb[par_sy.top_voice]; - sym_link(s) - curvoice = p_voice + } } Index: edit-de.js ================================================================== --- edit-de.js +++ edit-de.js @@ -8,11 +8,10 @@ stop: 'Stop', } var text_kv = { a: 'Über ...', b: 'ABC-Files:', - cmpa: 'Alles', cmpt: 'Stück', cmps: 'Auswahl', cmpl: 'Schleife', cmpc: 'Weiter', // df: 'abc2svg features', Index: edit-en.js ================================================================== --- edit-en.js +++ edit-en.js @@ -6,11 +6,10 @@ load: 'Please, load the included file ', } var text_kv = { a: 'About', b: 'ABC files:', - cmpa: 'All', cmpt: 'Tune', cmps: 'Selection', cmpl: 'Loop', cmpc: 'Continue', // df: 'abc2svg features', Index: edit-fr.js ================================================================== --- edit-fr.js +++ edit-fr.js @@ -6,11 +6,10 @@ load: 'SVP, ouvrez le fichier à inclure ', } var text_kv = { a: 'A propos', b: 'Fichiers ABC:', - cmpa: 'Tout', cmpt: 'Morceau', cmps: 'Selection', cmpl: 'Boucle', cmpc: 'Continuer', // df: 'abc2svg features', Index: edit-it.js ================================================================== --- edit-it.js +++ edit-it.js @@ -6,11 +6,10 @@ load: 'Carica il file da includere ', } var text_kv = { a: 'A proposito', b: 'File ABC:', - cmpa: 'Tutto', cmpt: 'Brano', cmps: 'Selezione', cmpl: 'Loop', cmpc: 'Continua', // df: 'Caratteristiche di abc2svg', Index: edit-pt_BR.js ================================================================== --- edit-pt_BR.js +++ edit-pt_BR.js @@ -6,11 +6,10 @@ load: 'Por favor, inclua o arquivo ', } var text_kv = { a: 'Sobre', b: 'Arquivos ABC:', - cmpa: 'Todo', cmpt: 'Peça', cmps: 'Selecção', cmpl: 'Loop', cmpc: 'Continua', // df: 'abc2svg features', Index: editor/edit.css ================================================================== --- editor/edit.css +++ editor/edit.css @@ -1,8 +1,7 @@ /* edit.css - file used in the abc2svg editor */ @page {margin:0} -text tspan {white-space:pre; fill:currentColor} svg {display:block;background-color:#ffffff} .abcr {fill: #d00000; fill-opacity: 0; z-index: 15} @media screen { body, html {height: 90%} text, path {pointer-events: none} 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-2021 Jean-Francois Moine +// Copyright (C) 2014-2022 Jean-Francois Moine // // This file is part of abc2svg. // // abc2svg is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -212,14 +212,15 @@ elt_ref.diverr.innerHTML = ''; selx[0] = selx[1] = 0; render2() } function render2() { - var content = elt_ref.source.value + var content = elt_ref.source.value, + def = (abc2svg.a_inc && abc2svg.a_inc["default.abc"]) || '' // load the required modules - if (!abc2svg.modules.load(content + elt_ref.src1.value, render2)) + if (!abc2svg.modules.load(content + elt_ref.src1.value + def, render2)) return // if page formatting, define a function to get the modification date if (abc2svg.modules.pageheight.loaded) { abc2svg.get_mtime = function(fn) { @@ -233,10 +234,22 @@ abc = new abc2svg.Abc(user); abc_images = ''; // document.body.style.cursor = "wait"; syms = [] + + // insert the file default.abc if loaded + if (abc2svg.a_inc && abc2svg.a_inc["default.abc"]) { + try { + abc.tosvg("default.abc", abc2svg.a_inc["default.abc"]) + } catch(e) { + alert(e.message + '\nabc2svg tosvg bug - stack:\n' + e.stack) + return + } + } + + // render the textarea try { abc.tosvg(abc_fname[0], content) } catch(e) { alert(e.message + '\nabc2svg tosvg bug - stack:\n' + e.stack) return @@ -274,12 +287,15 @@ return o } // soffs() // select a source ABC element on error function gotoabc(l, c) { + var s = elt_ref.source + selsrc(0) - c = soffs(l, Number(c)) + if (l >= 0) + c = soffs(l, Number(c)) s.focus(); s.setSelectionRange(c, syms[c] ? syms[c].iend : c + 1) } // click in the target @@ -287,12 +303,12 @@ var v, cl = evt.target.getAttribute('class') play.loop = false; - evt.stopImmediatePropagation(); - evt.preventDefault() +// evt.stopImmediatePropagation(); +// evt.preventDefault() // remove the context menu if active if (ctxMenu && ctxMenu.style.display == "block") { ctxMenu.style.display = "none" return @@ -584,12 +600,12 @@ sym = sym.ts_prev } // not reached } - function gsot(si) { // go to the first playable symbol of a tune - return gnrn(syms[si].p_v.sym) + function gsot(si) { // go to the first symbol of a tune + return syms[si].p_v.sym } function get_se(si) { // get the starting symbol var sym = syms[si] while (!sym.seqst) @@ -680,10 +696,11 @@ play_start(si, ei) } // set the version and initialize the playing engine function edit_init() { + var a, i, e // loop until abc2svg is fully loaded if (typeof abc2svg != "object" || !abc2svg.modules) { setTimeout(edit_init, 500) @@ -740,13 +757,13 @@ document.getElementById("abc2svg").innerHTML = 'abc2svg-' + abc2svg.version + ' (' + abc2svg.vdate + ')' // keep references on the page elements - var a = ["diverr", "source", "src1", "s0", "s1", "target"] - for (var i = 0; i < a.length; i++) { - var e = a[i]; + a = ["diverr", "source", "src1", "s0", "s1", "target"] + for (i = 0; i < a.length; i++) { + e = a[i] elt_ref[e] = document.getElementById(e) } // set the callback functions document.getElementById("saveas").onclick = saveas; @@ -787,17 +804,18 @@ play.abcplay.set_vol() * 10) document.getElementById("gvl").setAttribute("value", (play.abcplay.set_vol() * 10).toFixed(2)) }); - var e = elt_ref.target; - e.oncontextmenu = function(evt) { + // display the play menu + // This function is called on non left click + function show_menu(evt) { var x, y, elt = evt.target, - cl = elt.getAttribute('class'); + cl = elt.getAttribute('class') - evt.stopImmediatePropagation(); +// evt.stopImmediatePropagation(); evt.preventDefault() // if right click on an element, select it if (cl && cl.substr(0, 4) == 'abcr') { setsel(1, Number(cl.slice(6, -1))) @@ -815,12 +833,17 @@ x = evt.pageX - elt_ref.target.parentNode.offsetLeft + elt_ref.target.parentNode.scrollLeft; y = evt.pageY + elt_ref.target.parentNode.scrollTop; ctxMenu.style.left = (x - 30) + "px"; ctxMenu.style.top = (y - 10) + "px" - return false - } // oncontextmenu + } // showmenu + + e = elt_ref.target + e.onauxclick = show_menu // right or middle buttons + e.oncontextmenu = function(ev) { + ev.preventDefault() + } } set_pref() // set the preferences from local storage } // drag and drop @@ -853,13 +876,16 @@ return } // check if file data = evt.dataTransfer.files // FileList object. if (data.length) { - var reader = new FileReader(); + var reader = new FileReader(), + s = srcidx == 0 ? "source" : "src1" + + elt_ref["s" + srcidx].value = abc_fname[srcidx] = data[0].name reader.onload = function(evt) { - elt_ref.source.value = evt.target.result; + elt_ref[s].value = evt.target.result src_change() } reader.readAsText(data[0],"UTF-8") // return } Index: editor/edit.xhtml ================================================================== --- editor/edit.xhtml +++ editor/edit.xhtml @@ -4,10 +4,11 @@ + ABC edit with abc2svg @@ -63,11 +64,11 @@