Index: README.md ================================================================== --- README.md +++ README.md @@ -1,259 +1,537 @@ + ## abc2svg -**abc2svg** is a set of tools written in Javascript and based on -[abcm2ps](https://github.com/leesavide/abcm2ps). - -It permits to edit, display, print and play music from files written in -[ABC](http://abcnotation.com/). - -Its specific features are described (with abc2svg!) in the document -[abcm2ps/abc2svg features](http://moinejf.free.fr/abcm2ps-doc/features.xhtml) -and the parameters in -[abcm2ps/abc2svg parameters](http://moinejf.free.fr/abcm2ps-doc/index.html). - -### Web usage - -**abc2svg** may be used in any web browser. -The needed files are available in my site -[http://moinejf.free.fr/js/](http://moinejf.free.fr/js). -They are updated on release change. - -These files are: - -- `abc2svg-1.js` - This script is the **abc2svg** core. - It contains the ABC parser and the SVG generation engine. - It must be included in the (X)HTML header of the pages - where ABC rendering is needed (in ` + + +in the HTML <head> and put the music as +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" + +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: +`        svg { display: block }` + +This permits to have the music on separate vertical areas. +Without this CSS, the music is in-lined as in [this other example][5]. + +[5]: http://moinejf.free.fr/abcm2ps-doc/dansou-i.html "Tune index example" + +Global ABC parameters may be added as `parameter=value` in the query string +of the URL of the page. The [following example][16] calls the same +"J'ai du bon tabac" with `pagescale=1.2` (giving `%%pagescale 1.2`). + +[16]: http://moinejf.free.fr/abcm2ps-doc/tabac.xhtml?pagescale=1.2 "J'ai du bon tabac" + +In the above examples, all the ABC music is generated (displayed +and ready to be played) by means of the script **abcweb-1.js**. +If there is a large collection of tunes, it may be preferable to +link to the other script, **abcweb1-1.js**, which permits you +to select particular tune(s). Without an explicit +selection (see below), a list of the tunes is displayed, and, +after a tune has been selected, the whole page is replaced by the music. +(The HTML code, if any, is not displayed.) +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. +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 +can be accomplished by ending the URL with the character '`#`' +followed by a [regular expression][7] +to be applied to the tune headers. +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 +printed pages. + +#### 1.4 Installing abc2svg on your system or on your server + +The **abc2svg** package on my server is still being tuned +which could change its behaviour at any unknown time; so you may +prefer to install and run it from your own system. + +There are many ways to install abc2svg: + +- [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). + +- 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) + +- fossil clone + If you can get the fossil program (one binary) for your system, + you may clone the chisel repository by + `        fossil clone https://chiselapp.com/user/moinejf/repository/abc2svg abc2svg.fossil` + `        fossil open abc2svg.fossil` + and get the abc2svg source files containing the last changes + between the official versions. Updating your files is done by + `        fossil pull` + `        fossil update` + Building the scripts is done in the same way as with a tarball. + The repository is presently over 23Mb. + + For those unfamiliar with [fossil][14], it is an integrated + software management system similar to [git](https://git-scm.com/). + Chisel acts like a repository similar to [github](https://github.com/). + +[14]: https://fossil-scm.org/home/doc/trunk/www/index.wiki + +- npm + The scripts may be installed from the `npm` repository by: + `        npm install abc2svg` + They are ready to run (web and shell with nodejs). + I upload a new npm version about once a month. + +Using bookmarklets with a local installation does not work directly +because of a cross-domain security hole, but this is possible by running +a local HTTP server (you will also have to change the location +of the scripts in the bookmarklet code). + +If you have write access on a remote server, you may put there +the abc2svg scripts. There is no automatic process to do that. +You will have to +[look at my site](http://moinejf.free.fr/js/ "all abc2svg scripts") +to determine the files that need to be copied. + +In addition, you have to set the correct location of the abc2svg scripts +in your pages. As a trick, I put the abc2svg scripts in a folder +at the same level as the HTML files: + +`        ` + +This allows the generation of the music to run either locally +or remotely. + +### 2. Automatic creation of music sheets + +#### 2.1 abc2svg shell scripts + +As you have seen, printing the music can be done easily with any web browser. +You can automate the process of creating music sheets +with **abc2svg** using shell scripts running a Javascript interpreter. + +The interfaces to the various scripts are different. Below you will +find various scripts that I had to built. + +- `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) -- `abcnode` with `node` (nodeJS) - `abcv8` with `d8` (Google libv8) +- `abcnode` with `node` (nodeJS without module) +- `abc2svg` with `node` (nodeJS with modules) + +[10]: https://bellard.org/quickjs/ + +Each script gets the abc2svg options and ABC files from the command line +and sends the generated file to `stdout` and possible errors to `stderr`. +The general syntax of the command line is: +`        script [script.js] [options] ABC_file [[options] ABC_file]* [options]` +with: + +- `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. -#### backend scripts +#### 2.2 Backend scripts -By default, the batch scripts generate (XHTML+SVG) files. +By default, the shell scripts generate (HTML+SVG) files. This output may be modified by backend scripts. These ones must appear -just after the command. -There are: - -- `toabc.js` - This script outputs back the (selected) ABC tunes of the ABC source file. - Transposition is applied. - The resulting file does not contain the formatting parameters. - Example: +immediately following the name of the shell script. +They are: + +- `toabc.js` + This script returns the (selected) ABC tunes from the ABC source file + applying transposition. + The resulting file does not contain the formatting parameters. + Example: `        abcqjs toabc.js my_file.abc --select X:2 > tune_2.abc` -- `toabw.js` - This script outputs a Abiword file (ABW+SVG) which may be read by some - word processors (abiword, libreoffice...) and converted to many other - formats by the batch function of abiword. +- `toabw.js` + This script outputs an Abiword file (ABW+SVG) that can be read by some + 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: + in the local system for displaying and/or converting the .abw file. + Example: `        abcv8 toabw.js my_file.abc > my_file.abw` -- `tomei.js` - This script outputs the music as a [MEI](https://music-encoding.org/) file. - Indeed, only one tune may be translated from ABC to MEI (multi-tunes ABC +- `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). -- `toodt.js` - This script creates an Open Document (ODT+SVG) which may be read by most - word processors (abiword, libreoffice...). - It runs only with the npm script `abc2svg` and asks for the npm module - `jszip` to be installed. - The output ODT document may be specified by the command line argument `-o` - (default `abc.odt`). - Example: +- `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. + The output ODT document may be specified in the command line argument + after `-o` (default `abc.odt`). + Example: `        abc2svg toodt.js my_file.abc -o my_file.odt` -- `toparam.js` +- `toparam.js` This script just outputs the abc2svg parameters. -#### PDF generation +#### 2.3 PDF generation `abctopdf` is a shell script which converts ABC to PDF using one of the -previous shell scripts and the program `rsvg-convert`. -As a constraint, the used music font must be installed and defined by -`%%musicfont `. -Example: +previous shell scripts and, either a chrome/chromium compatible web browser +(settable by the environment variable 'BROWSER'), +or the program [weasyprint](https://weasyprint.org/) or +the program `rsvg-convert`. + +With `rsvg-convert`, the used music font must be installed and defined by +`%%musicfont `. + +Note also that, with `weasyprint` or `rsvg-convert`, the paper size is +forced to A4. Instructions for changing this size may be found in the +script source. + +The output PDF document may be specified by the command line argument `-o` +(default `abc.pdf`). + +Example: `        abctopdf my_file.abc -o my_file.pdf` -### MEI support +### 3. Build + +The abc2svg scripts which are used to render the music +either by a web browser or by a shell script must be built from +the source files you got by tarball or fossil clone. + +Quoting [Douglas Crockford](https://www.crockford.com/jsmin.html), +minification is a process that removes comments and unnecessary whitespace from +JavaScript files. It typically reduces file size by half, resulting in +faster downloads. + +If you can run one of the tools [ninja](https://ninja-build.org/) +or [samurai](https://github.com/michaelforney/samurai), you can build +the scripts + +- without minification + This is useful for debugging purposes and the scripts are more human friendly. + + `        NOMIN=1 samu -v` + + or + + `        NOMIN=1` + `        export NOMIN` + `        ninja -v` + +- in a standard way with minification + In this case, you need one of the tools + [JSMin](https://www.crockford.com/jsmin.html) or + `uglifyjs` which comes with nodeJS. + + `        samu -v` + +If you also want to change or add music glyphs, you may edit the source +file `font/abc2svg.sfd`. +In this case, you will need both `base64` and `fontforge`, and run + +`        samu -v font.js` + +If you cannot or don't want to install `ninja` or `samurai`, you may build +the abc2svg files by the shell script `./build`. +(This script must be run by a Posix compatible shell.) + +### 4. Inside the code of abc2svg + +#### 4.1 Core and modules + +`abc2svg-1.js` is the **abc2svg** core. +It contains the ABC parser and the SVG generation engine. +It is needed for all music rendering. It is automatically loaded +by the web scripts and the shell scripts. +If you want to use the core with your own scripts, +its API is described in the [wiki][11]. + +[11]: https://chiselapp.com/user/moinejf/repository/abc2svg/wiki?name=interface-1 + +The core does not handle all the abc2svg commands/parameters. +Some of them are treated by modules. +A module is a script which is loaded in the browser or in the JS interpreter +when the command it treats is seen in the ABC flow. +Detailed information about the modules may be found in the [wiki][12]. + +[12]: https://chiselapp.com/user/moinejf/repository/abc2svg/wiki?name=modules + +#### 4.2 Internal information + +- The music is displayed as SVG images. There is one image per + music line / text block. + If you want to move these images to some other files, + each one must contain the full CSS and defs. For that, insert + `        %%fullsvg x` + in the ABC file before rendering (see the + [fullsvg documentation](http://moinejf.free.fr/abcm2ps-doc/fullsvg.xhtml) + for more information). + +- Playing uses the HTML5 audio and/or the midi APIs. + For audio, by default, abc2svg uses a sound font (format SF2) + which is split into one file + per instrument. This sound font is stored in the subdirectory `Scc1t2/`. + Each instrument file is a base64 encoded javascript array. + + Other sound fonts may be used. Some of them are stored in the subdirectory + `sf2/` (`AWE_ROM_gm` and `2MBGMGS`). Two formats are supported: raw SF2 and + SF2 wrapped into javascript (the raw SF2 files can be loaded + only when they are in the same HTTP domain). + The shell script `sf.sh` (in `sf2/`) may be used to create the javacript files + from raw SF2 files. + The sound font to be used for playing may be defined in the ABC code + by the command `%%soundfont`. E.g.: + `        %%soundfont http://moinejf.free.fr/js/sf2/AWE_ROM_gm.js` + +- The names of the abc2svg scripts have a suffix which is the version of + the core interface (actually '`-1`'). + +#### 4.3 More about the web scripts + +Here are the scripts which are used in a web context: + +- `abcweb-1.js` + This script replaces the ABC or MEI sequences found in the (X)HTML file + by SVG images of the music. The ABC sequences 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 + 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) + for an example, and here is [how to put inline music in HTML][15]. + Playing and highlighting the played notes may be offered loading + the script `snd-1.js` (see below). + +[15]: http://moinejf.free.fr/abcm2ps-doc/inline.html "abc2svg - inline music" + +- `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 + of the tunes. The list step may be bypassed when the URL of the file + contains a regular expression in the 'hash' value ('#' followed by + a string at the end of the URL). + This string does a + [%%select](http://moinejf.free.fr/abcm2ps-doc/select.xhtml). + When one or many tunes are displayed, a menu in the top/right corner + offers to go back to the tune list or to modify the ABC source. + +- `snd-1.js` + This script may be used with `abcweb{,1}-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) + for an example. + +### 5. MEI support As an experimental feature, an extented core `mei2svg-1.js` may be generated. -This one may handle both the ABC and [MEI](https://music-encoding.org/) notations. - -In browser mode, the script `abcemb-1.js` loads either abc2svg-1.js or -mei2svg-1.js after checking the music notation type (`%abc` or `X:` is ABC, -` + +- JavaScript SoundFont 2 Parser by imaya/GREE Inc and Colin Clark + + +- Scc1t2 + + +- strftime by T. H. Doan + + +[Jean-François Moine](http://moinejf.free.fr) Index: abc2odt ================================================================== --- abc2odt +++ abc2odt @@ -7,15 +7,14 @@ function usage() { abc2svg.printErr('ABC to ODT translator with\ abc2svg-' + abc2svg.version + ' ' + abc2svg.vdate + '\n\ Usage:\n\ - abc2odt [g_options] ABC_file [f_options] [ABC_file [f_options]]* [-o output_file]\n\ + abc2odt [options] ABC_file [[options] ABC_file]* [options] [-o output_file]\n\ Arguments:\n\ - g_options global ABC options\n\ + options ABC options (the last options are moved before the last file)\n\ ABC_file ABC file\n\ - f_options file options\n\ output_file output file - default is "./abc.odt"') abc2svg.quit() } // load the abc2svg core Index: abc2svg ================================================================== --- abc2svg +++ abc2svg @@ -1,25 +1,12 @@ -#! /usr/bin/env node +#!/usr/bin/env node // abc2svg with native nodeJS // define the system functions var sep = require('path').sep -function usage() { - abc2svg.printErr('ABC translator with Node.js\ - and abc2svg-' + abc2svg.version + ' ' + abc2svg.vdate + '\n\ -Usage:\n\ - abc2svg [script.js] [g_options] ABC_file [f_options] [ABC_file [f_options]]*\n\ -Arguments:\n\ - script.js generation script to load - default: toxhtml.js (XHTML+SVG)\n\ - g_options global ABC options\n\ - ABC_file ABC file\n\ - f_options file options') - abc2svg.quit() -} - // load the abc2svg core var m = require("abc2svg/abc2svg-1"); global.abc2svg = m.abc2svg; global.Abc = m.Abc; @@ -53,11 +40,11 @@ relay() } catch(e) { if (onerror) onerror() else - printErr('Cannot read file ' + fn) + abc2svg.printErr('Cannot read file ' + fn) } } // load the command line m = require("abc2svg/cmdline"); @@ -64,23 +51,9 @@ global.user = m.user; global.abc_cmd = m.abc_cmd // main var args = process.argv -if (!args[2]) - usage(); args.shift(); args.shift() -// if the first argument is a script, load it -if (/\.js$/.test(args[0])) { - var fn = args[0][0] == '/' ? args[0] : (__dirname + '/' + args[0]), - f = abc2svg.readFile(fn); - eval(f); - args.shift() -} - -// if no rendering engine, load xhtml -if (typeof(abc2svg.abc_init) != 'function') - require("abc2svg/toxhtml"); - -abc_cmd("nodeJS", args) +abc_cmd("nodeJS", args, "Node.js") Index: abc2svg.ttf ================================================================== --- abc2svg.ttf +++ abc2svg.ttf cannot compute difference between binary files Index: abcjs24 ================================================================== --- abcjs24 +++ abcjs24 @@ -1,26 +1,24 @@ #!/usr/bin/env js24 // abc2svg with js24 (Mozilla JavaScript shell - Spidermonkey // packages: debian: libmozjs-24-bin, voidlinux: mozjs24) -loadRelativeToScript("abc2svg-1.js") - // interpreter specific functions -abc2svg.print = function(str) { +var abc2svg = { + print: function(str) { print(str) -} -abc2svg.printErr = function(str) { + }, + printErr: function(str) { printErr(str) -} - -abc2svg.quit = function() { + }, + quit: function() { quit(1) -} -abc2svg.readFile = function(fname) { + }, + readFile: function(fname) { return read(fname) -} -abc2svg.loadjs = function(fn, relay, onerror) { + }, + loadjs: function(fn, relay, onerror) { try { loadRelativeToScript(fn) if (relay) relay() } catch(e) { @@ -27,31 +25,14 @@ if (onerror) onerror() else printErr('Cannot read file ' + fn) } -} // loadjs() + } // loadjs() +} // abc2svg +loadRelativeToScript("abc2svg-1.js") loadRelativeToScript("cmdline.js") // main - var args = scriptArgs - - if (/\.js$/.test(args[0])) { - load(args[0]) - args.shift() - } - if (typeof abc2svg.abc_init != 'function') - loadRelativeToScript("toxhtml.js") - if (!args[0]) { - printErr('ABC translator with js24 (SpiderMonkey)\ - and abc2svg-' + abc2svg.version + ' ' + abc2svg.vdate + '\n\ -Usage:\n\ - abcjs24 [script.js] [g_options] ABC_file [f_options] [ABC_file [f_options]]*\n\ -Arguments:\n\ - script.js generation script to load - default: toxhtml.js (XHTML+SVG)\n\ - g_options global ABC options\n\ - ABC_file ABC file\n\ - f_options file options') - quit() - } - abc_cmd("abcjs24", args) + + abc_cmd("abcjs24", scriptArgs, "js24 (SpiderMonkey)") Index: abcjs52 ================================================================== --- abcjs52 +++ abcjs52 @@ -1,26 +1,24 @@ #!/usr/bin/env js52 // abc2svg with js52 (Mozilla JavaScript shell - Spidermonkey // packages: debian: libmozjs-52-dev, voidlinux: mozjs52) -loadRelativeToScript("abc2svg-1.js") - // interpreter specific functions -abc2svg.print = function(str) { +var abc2svg = { + print: function(str) { print(str) -} -abc2svg.printErr = function(str) { + }, + printErr: function(str) { printErr(str) -} - -abc2svg.quit = function() { + }, + quit: function() { quit(1) -} -abc2svg.readFile = function(fname) { + }, + readFile: function(fname) { return read(fname) -} -abc2svg.loadjs = function(fn, relay, onerror) { + }, + loadjs: function(fn, relay, onerror) { try { loadRelativeToScript(fn) if (relay) relay() } catch(e) { @@ -27,31 +25,14 @@ if (onerror) onerror() else printErr('Cannot read file ' + fn) } -} // loadjs() + } // loadjs() +} // abc2svg +loadRelativeToScript("abc2svg-1.js") loadRelativeToScript("cmdline.js") // main - var args = scriptArgs - - if (/\.js$/.test(args[0])) { - load(args[0]) - args.shift() - } - if (typeof abc2svg.abc_init != 'function') - loadRelativeToScript("toxhtml.js") - if (!args[0]) { - printErr('ABC translator with js52 (SpiderMonkey)\ - and abc2svg-' + abc2svg.version + ' ' + abc2svg.vdate + '\n\ -Usage:\n\ - abcjs52 [script.js] [g_options] ABC_file [f_options] [ABC_file [f_options]]*\n\ -Arguments:\n\ - script.js generation script to load - default: toxhtml.js (XHTML+SVG)\n\ - g_options global ABC options\n\ - ABC_file ABC file\n\ - f_options file options') - quit() - } - abc_cmd("abcjs52", args) + + abc_cmd("abcjs52", scriptArgs, "js52 (SpiderMonkey)") Index: abcjs60 ================================================================== --- abcjs60 +++ abcjs60 @@ -1,26 +1,24 @@ #!/usr/bin/env js60 // abc2svg with js60 (Mozilla JavaScript shell - Spidermonkey // packages: debian: libmozjs-60-dev, voidlinux: mozjs60) -loadRelativeToScript("abc2svg-1.js") - // interpreter specific functions -abc2svg.print = function(str) { +var abc2svg = { + print: function(str) { print(str) -} -abc2svg.printErr = function(str) { + }, + printErr: function(str) { printErr(str) -} - -abc2svg.quit = function() { + }, + quit: function() { quit(1) -} -abc2svg.readFile = function(fname) { + }, + readFile: function(fname) { return read(fname) -} -abc2svg.loadjs = function(fn, relay, onerror) { + }, + loadjs: function(fn, relay, onerror) { try { loadRelativeToScript(fn) if (relay) relay() } catch(e) { @@ -27,31 +25,14 @@ if (onerror) onerror() else printErr('Cannot read file ' + fn) } -} // loadjs() + } // loadjs() +} // abc2svg +loadRelativeToScript("abc2svg-1.js") loadRelativeToScript("cmdline.js") // main - var args = scriptArgs - - if (/\.js$/.test(args[0])) { - load(args[0]) - args.shift() - } - if (typeof abc2svg.abc_init != 'function') - loadRelativeToScript("toxhtml.js") - if (!args[0]) { - printErr('ABC translator with js60 (SpiderMonkey)\ - and abc2svg-' + abc2svg.version + ' ' + abc2svg.vdate + '\n\ -Usage:\n\ - abcjs60 [script.js] [g_options] ABC_file [f_options] [ABC_file [f_options]]*\n\ -Arguments:\n\ - script.js generation script to load - default: toxhtml.js (XHTML+SVG)\n\ - g_options global ABC options\n\ - ABC_file ABC file\n\ - f_options file options') - quit() - } - abc_cmd("abcjs60", args) + + abc_cmd("abcjs60", scriptArgs, "js60 (SpiderMonkey)") Index: abcjsc ================================================================== --- abcjsc +++ abcjsc @@ -30,66 +30,49 @@ // } else { // fout += String.fromCharCode(((b1 & 0x1f) << 6) + // (b2 & 0x3f)) // } // } else { -// fout += fin[i] +// fout += String.fromCharCode(b1) // } // } // return fout //} -load(progdir + 'abc2svg-1.js') - // interpreter specific functions -abc2svg.print = function(str) { +var abc2svg = { + print: function(str) { print(str) -} -abc2svg.printErr = function(str) { + }, + printErr: function(str) { printErr(str) -} -abc2svg.quit = function() { + }, + quit: function() { quit(1) -} -abc2svg.readFile = function(fname) { + }, + readFile: function(fname) { return read(fname) -} -abc2svg.loadjs = function(fn, relay, onerror) { + }, + loadjs: function(fn, relay, onerror) { try { load(progdir + fn) if (relay) relay() } catch(e) { if (onerror) onerror() else - print('Cannot read file ' + fn) + printErr('Cannot read file ' + fn) } -} + } // loadjs() +} // abc2svg +load(progdir + 'abc2svg-1.js') load(progdir + 'cmdline.js') function main(args) { - if (/\.js$/.test(args[0])) { - load(args[0]) - args.shift() - } - if (!args[0]) { - print('ABC translator with jsc (webkit2gtk)\ - and abc2svg-' + abc2svg.version + ' ' + abc2svg.vdate + '\n\ -Usage:\n\ - abcjsc [script.js] [g_options] ABC_file [f_options] [ABC_file [f_options]]*\n\ -Arguments:\n\ - script.js script to load - default: toxhtml.js (XHTML+SVG generation)\n\ - g_options global ABC options\n\ - ABC_file ABC file\n\ - f_options file options') - quit() - } - if (typeof(abc2svg.abc_init) != 'function') - load(progdir + 'toxhtml.js') - abc_cmd('abcjsc', args) + abc_cmd('abcjsc', args, 'jsc (webkit2gtk)') } if (typeof arguments == 'undefined') var arguments = '' Index: abcnode ================================================================== --- abcnode +++ abcnode @@ -1,18 +1,20 @@ -#!/bin/sh -# abc2svg with raw nodeJS +:;# abc2svg with raw nodeJS progdir=`dirname $0` -cat ${progdir}/abc2svg-1.js > /tmp/toto.js +cat ${progdir}/abc2svg-1.js ${progdir}/cmdline.js > /tmp/toto.js case $1 in *.js) cat $1 >> /tmp/toto.js + if grep 'abc2svg.abc_init =' "$1" > /dev/null; then + x="no" + fi shift ;; esac -if ! grep 'abc2svg.abc_init =' "$1" > /dev/null; then - cat ${progdir}/toxhtml.js >> /tmp/toto.js +if [ "x$x" != 'xno' ]; then + cat ${progdir}/tohtml.js >> /tmp/toto.js fi cat <> /tmp/toto.js var progdir='${progdir}/' @@ -53,31 +55,15 @@ } // (this file is wrapped) global.Abc = Abc global.abc2svg = abc2svg -EOF -cat ${progdir}/cmdline.js >> /tmp/toto.js - -cat <> /tmp/toto.js // main - var args = process.argv - if (!args[2]) { - abc2svg.print('ABC translator with nodeJS\ - and abc2svg-' + abc2svg.version + ' ' + abc2svg.vdate + '\n\ -Usage:\n\ - abcnode [script.js] [g_options] ABC_file [f_options] [ABC_file [f_options]]*\n\ -Arguments:\n\ - script.js generation script to load - default: toxhtml.js (XHTML+SVG)\n\ - g_options global ABC options\n\ - ABC_file ABC file\n\ - f_options file options'); - abc2svg.quit(1) - } + var args = process.argv args.shift() args.shift() global.user = user; // (for modules/page.js) - abc_cmd("abcnode", args) + abc_cmd("abcnode", process.argv, "nodeJS") EOF node /tmp/toto.js "$@" Index: abcqjs ================================================================== --- abcqjs +++ abcqjs @@ -1,116 +1,76 @@ :;# shell script to convert ABC or MEI music notation to other format # using abc2svg and quickjs (https://bellard.org/quickjs/) -test -f /tmp/abcqjs.js && exec qjs --std /tmp/abcqjs.js $0 "$@" - -cat > /tmp/abcqjs.js << EOF -//javascript +exec qjs --std -e ' var i, - args = scriptArgs - args.shift() // remove '/tmp/abcqjs.js' - var core = "abc2svg-1.js", + args = scriptArgs, + core = "abc2svg-1.js", path = args.shift(). // the 1st argument is the script name - match(/(.*\/).*/) + match(/.*\//) + +// interpreter specific functions +function load(fn) { + return std.loadScript(fn) +} +var abc2svg = { + print: function(str) { + print(str) + }, + 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() + get_mtime: function(fn) { + return new Date(os.stat(fn)[0].mtime) + }, + loadjs: function(fn, relay, onerror) { + try { + load(fn[0] == "/" ? fn : (path + fn)) + } 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[1] : "" + path = path ? path[0] : "" i = args.length while (--i >= 0) { if (/\.mei$/.test(args[i])) { core = "mei2svg-1.js" break } } -std.loadScript(path + core) - -// interpreter specific functions -function load(fn) { - return std.loadScript(fn) -} -abc2svg.print = function(str) { - print(str) -} -abc2svg.printErr = function(str) { - std.err.printf("%s\n", str) -} - -abc2svg.quit = function() { - std.exit(1) -} -abc2svg.readFile = function(fname) { - var l, f, - b = "", - bt = "", - cnt = 30 - - try { - f = std.open(fname, "r") - } catch(e) { - return "" - } - - while (1) { - if (--cnt < 0) { - cnt = 30 - bt += b - b = "" - } - l = f.getline() - if (l == null) - break - b += l + "\n" - } - f.close() - return bt + b -} // readfile() - -abc2svg.get_mtime = function(fn) { - return new Date(os.stat(fn)[0].mtime) -} - -abc2svg.loadjs = function(fn, relay, onerror) { - try { - std.loadScript(path + fn) - if (relay) - relay() - } catch(e) { - if (onerror) - onerror() - else - printErr('Cannot read file ' + fn) - } -} // loadjs() - -std.loadScript(path + "cmdline.js") - -// main - if (/\.js$/.test(args[0])) { - if (args[0][0] == "/") - std.loadScript(args[0]) - else - std.loadScript(path + args[0]) - args.shift() - } - if (typeof abc2svg.abc_init != 'function') - std.loadScript(path + "toxhtml.js") - if (!args[0]) { - abc2svg.printErr('ABC translator with quickjs\ - and abc2svg-' + abc2svg.version + ' ' + abc2svg.vdate + '\n\ -Usage:\n\ - abcqjs [script.js] [g_options] ABC_file [f_options] [ABC_file [f_options]]*\n\ -Arguments:\n\ - script.js generation script to load - default: toxhtml.js (XHTML+SVG)\n\ - g_options global ABC options\n\ - ABC_file ABC file\n\ - f_options file options') - abc2svg.quit() - } - abc_cmd("abcqjs", args) -EOF -exec qjs --std /tmp/abcqjs.js $0 "$@" + load(path + core) + load(path + "cmdline.js") + + abc_cmd("abcqjs", args, "QuickJS") +' $0 "$@" 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,20 +1,33 @@ -#!/bin/sh +:;# 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') +# - weasyprint (https://weasyprint.org/) +# - rsvg-convert (https://wiki.gnome.org/Projects/LibRsvg) # -# abctopdf - converts ABC to PDF using abc2svg +# Note: when weasyprint or rsvg-convert are 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 # -# Copyright (C) 2019 Jean-François Moine - License GPL3+ +# Copyright (C) 2019-2020 Jean-François Moine - License GPL3+ + +# set a browser +if [ x$BROWSER = x ]; then + BROWSER=chromium +fi if [ $# -eq 0 ]; then cat << EOF ABC to PDF translator using abc2svg Usage: - abctopdf [g_options] ABC_file [f_options] [ABC_file [f_options]]* [-o output_file] + abctopdf [options] ABC_file [[options] ABC_file] [options] [-o output_file] Arguments: - g_options global ABC options + options ABC options (the last options are moved before the last file) ABC_file ABC file - f_options file options output_file output file - default is "./abc.pdf" EOF exit 1 fi @@ -73,21 +86,72 @@ fi shift done # check if some chromium-based browser is available -# test rsvg-convert -for c in chromium-browser vivaldi opera brave-browser epic google-chrome; do +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.xhtml - $c --headless --disable-gpu --print-to-pdf=$out /tmp/o.xhtml - rm /tmp/o.xhtml + command $abcscr "$@" > /tmp/o.html + $c --headless --disable-gpu --print-to-pdf=$out /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 + +# 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 +command $abcscr --pageheight 29.7cm --fullsvg 1 --musicfont abc2svg "$@" | while read v; do + case "$v" in + " $fn + echo "" >> /tmp/abc.html + ;; + "> $fn + fn= + ;; + "> $fn + else + echo $v >> /tmp/abc.html + fi + ;; + esac +done + echo '' >> /tmp/abc.html + +echo weasyprint -f pdf /tmp/abc.html $out + 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" exit 1 fi @@ -131,10 +195,11 @@ 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, @@ -142,11 +207,11 @@ if (!sty) return p sty = sty[1].split('\n') if (!page_sty) - page_sty = '\n' + 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') } @@ -163,10 +228,15 @@ page_h = 0 page_out = "" break case "]*>/) // " + if (h) + page_cl = h[2] + } page_out += p .replace(//, '') .replace('', '\n') @@ -173,14 +243,16 @@ page_h += Number(p.match(/height="(\d+)px"/)[1]) break case "/dev/null 2>&1; then +# binary program +gen() { + cat "$@" | jsmin +} +elif command -v qjs >/dev/null 2>&1; then +# script using QuickJS +if [ ! -f jsmin-min.js ] || [ jsmin-min.js -ot jsmin.js ]; then + qjs --std jsmin.js < jsmin.js > jsmin-min.js +fi +gen() { + cat "$@" | qjs --std jsmin-min.js +} +else +# no minify +gen() { + cat "$@" +} +fi out=abc2svg-$MAJOR.js VERSION=`grep '"version"' package.json|cut -d'"' -f4` -DATE=`date '+%Y-%m-%d' -r package.json` +DATE=`date -r package.json '+%Y-%m-%d'` cp core/license $out -cat core/abc2svg.js core/deco.js core/draw.js\ +gen core/abc2svg.js core/deco.js core/draw.js\ font.js core/format.js core/front.js core/music.js core/parse.js core/subs.js\ core/svg.js core/tune.js core/lyrics.js core/gchord.js core/tail.js\ core/modules.js >> $out echo "abc2svg.version=\"$VERSION\";abc2svg.vdate=\"$DATE\"" >> $out -cp modules/ambitus.js ambitus-$MAJOR.js -cp modules/break.js break-$MAJOR.js -cp modules/capo.js capo-$MAJOR.js -cat util/midi.js modules/clair.js >> clair-$MAJOR.js -cp modules/clip.js clip-$MAJOR.js -cp modules/combine.js combine-$MAJOR.js -cp modules/diag.js diag-$MAJOR.js -cp modules/equalbars.js equalbars-$MAJOR.js -cp modules/grid.js grid-$MAJOR.js -cp modules/grid2.js grid2-$MAJOR.js -cp modules/MIDI.js MIDI-$MAJOR.js -cat modules/page.js modules/strftime.js > page-$MAJOR.js -cp modules/perc.js perc-$MAJOR.js -cat modules/wps.js modules/psvg.js > psvg-$MAJOR.js -cp modules/soloffs.js soloffs-$MAJOR.js -cp modules/sth.js sth-$MAJOR.js -cp modules/temper.js temper-$MAJOR.js +out=mei2svg-$MAJOR.js +cp core/license $out +gen core/abc2svg.js core/deco.js core/draw.js\ + font.js core/format.js core/front.js core/music.js core/parse.js core/subs.js\ + core/svg.js core/tune.js core/lyrics.js core/gchord.js core/mei.js core/tail.js\ + core/modules.js >> $out + echo "abc2svg.version=\"$VERSION\";abc2svg.vdate=\"$DATE\"" >> $out + +gen modules/ambitus.js > ambitus-$MAJOR.js +gen modules/break.js > break-$MAJOR.js +gen modules/capo.js > capo-$MAJOR.js +gen modules/chordnames.js > chordnames-$MAJOR.js +gen modules/clair.js > clair-$MAJOR.js +gen modules/clip.js > clip-$MAJOR.js +gen modules/combine.js > combine-$MAJOR.js +gen modules/diag.js > diag-$MAJOR.js +gen modules/equalbars.js > equalbars-$MAJOR.js +gen modules/gamelan.js > gamelan-$MAJOR.js +gen modules/grid.js > grid-$MAJOR.js +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/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/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 cp editor/edit.css edit-$MAJOR.css -cp util/abcdoc.js abcdoc-$MAJOR.js -cp util/abcemb.js abcemb-$MAJOR.js -cp util/abcemb1.js abcemb1-$MAJOR.js -cp util/abcemb2.js abcemb2-$MAJOR.js -cp util/abcweb2.js abcweb2-$MAJOR.js -cp util/follow.js follow-$MAJOR.js -cp util/json.js json-$MAJOR.js -cp util/midi.js setmidi-$MAJOR.js -cp util/toaudio.js toaudio-$MAJOR.js -cat util/play.js util/toaudio.js util/toaudio5.js util/midi.js\ - util/sf2-parser.js util/tomidi5.js > play-$MAJOR.js -cat util/play.js util/sndgen.js util/sndaud.js util/midi.js\ - util/sf2-parser.js util/sndmid.js > snd-$MAJOR.js +gen util/abcdoc.js > abcdoc-$MAJOR.js +gen util/abcemb.js > abcemb-$MAJOR.js +gen util/abcemb1.js > abcemb1-$MAJOR.js +gen util/abcemb2.js > abcemb2-$MAJOR.js +gen util/abcweb.js > abcweb-$MAJOR.js +gen util/abcweb1.js > abcweb1-$MAJOR.js +gen util/abcweb.js > abcweb2-$MAJOR.js +gen util/chord.js > chord-$MAJOR.js +gen util/json.js > json-$MAJOR.js +gen util/toaudio.js > toaudio-$MAJOR.js +gen util/play.js util/toaudio.js util/toaudio5.js\ + util/sf2-parser.js util/tomidi5.js util/follow.js > play-$MAJOR.js +gen util/play.js util/sndgen.js util/sndaud.js\ + util/sf2-parser.js util/sndmid.js util/follow.js > snd-$MAJOR.js sed "s+@MAJOR@+$MAJOR+" editor/edit.xhtml > edit-$MAJOR.xhtml sed "s+@MAJOR@+$MAJOR+" editor/abckbd.js > abckbd-$MAJOR.js sed "s+@MAJOR@+$MAJOR+" editor/abckbd2.js > abckbd2-$MAJOR.js sed "s+@MAJOR@+$MAJOR+" editor/edit.js > edit-$MAJOR.js + +touch follow-$MAJOR.js Index: build.ninja ================================================================== --- build.ninja +++ build.ninja @@ -1,6 +1,6 @@ -# rules for ninja (ninja-build) +# rules for ninja (ninja-build) or samurai # minifying with 'jsmin' or 'uglifyjs' may be disabled by running # NOMIN=1 ninja -v MAJOR=1 @@ -22,19 +22,19 @@ base64 abc2svg.ttf | sed 's/$$/\\/' >> font.js;$ echo "\") format(\"truetype\")'" >> font.js rule jsc command = VERSION=`grep '"version"' package.json|cut -d'"' -f4`;$ - if command -v fossil >/dev/null 2>&1; then $ + if [ -e .fslckout ] && command -v fossil >/dev/null 2>&1; then $ c=`fossil info | grep checkout:`;$ t=`fossil info | grep tags: | awk '{print $$NF}'`;$ if [ "$${t#v}" != "$$VERSION" ]; then $ VERSION="$${VERSION}-$$(echo $$c | cut -d' ' -f2 | cut -c1-10)";$ fi;$ DATE=`echo $$c | cut -d' ' -f3`;$ else $ - DATE=`date '+%Y-%m-%d' -r package.json`;$ + DATE=`date -r package.json '+%Y-%m-%d'`;$ fi;$ cp core/license $out;$ if [ "x$$NOMIN" = "x" ]; then $ if command -v jsmin >/dev/null 2>&1; then $ cat $in | jsmin >> $out;$ @@ -96,64 +96,81 @@ core/modules.js | package.json build ambitus-$MAJOR.js: jscu modules/ambitus.js build break-$MAJOR.js: jscu modules/break.js build capo-$MAJOR.js: jscu modules/capo.js -build clair-$MAJOR.js: jscu util/midi.js modules/clair.js +build chordnames-$MAJOR.js: jscu modules/chordnames.js +build clair-$MAJOR.js: jscu modules/clair.js build clip-$MAJOR.js: jscu modules/clip.js build combine-$MAJOR.js: jscu modules/combine.js build diag-$MAJOR.js: jscu modules/diag.js build equalbars-$MAJOR.js: jscu modules/equalbars.js +build gamelan-$MAJOR.js: jscu modules/gamelan.js build grid-$MAJOR.js: jscu modules/grid.js build grid2-$MAJOR.js: jscu modules/grid2.js +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 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 soloffs-$MAJOR.js: jscu modules/soloffs.js build sth-$MAJOR.js: jscu modules/sth.js +build strtab-$MAJOR.js: jscu modules/strtab.js build temper-$MAJOR.js: jscu modules/temper.js build edit-$MAJOR.css: cp editor/edit.css build abcdoc-$MAJOR.js: jscu util/abcdoc.js build abcemb-$MAJOR.js: jscu util/abcemb.js build abcemb1-$MAJOR.js: jscu util/abcemb1.js build abcemb2-$MAJOR.js: jscu util/abcemb2.js -build abcweb2-$MAJOR.js: jscu util/abcweb2.js -build follow-$MAJOR.js: jscu util/follow.js +build abcweb-$MAJOR.js: jscu util/abcweb.js +build abcweb1-$MAJOR.js: jscu util/abcweb1.js +build abcweb2-$MAJOR.js: jscu util/abcweb.js +build chord-$MAJOR.js: jscu util/chord.js build json-$MAJOR.js: jscu util/json.js -build setmidi-$MAJOR.js: jscu util/midi.js build toaudio-$MAJOR.js: jscu util/toaudio.js build play-$MAJOR.js: jscu util/play.js util/toaudio.js $ - util/toaudio5.js util/midi.js util/sf2-parser.js util/tomidi5.js + util/toaudio5.js util/sf2-parser.js util/tomidi5.js util/follow.js build snd-$MAJOR.js: jscu util/play.js util/sndgen.js $ - util/sndaud.js util/midi.js util/sf2-parser.js util/sndmid.js + util/sndaud.js util/sf2-parser.js util/sndmid.js util/follow.js build edit-$MAJOR.xhtml: cpm editor/edit.xhtml build abckbd-$MAJOR.js: edit editor/abckbd.js build abckbd2-$MAJOR.js: edit editor/abckbd2.js build edit-$MAJOR.js: edit editor/edit.js build all: phony abc2svg-$MAJOR.js abcdoc-$MAJOR.js $ abcemb-$MAJOR.js abcemb1-$MAJOR.js abcemb2-$MAJOR.js $ - abcweb2-$MAJOR.js $ - json-$MAJOR.js $ - setmidi-$MAJOR.js toaudio-$MAJOR.js snd-$MAJOR.js play-$MAJOR.js psvg-$MAJOR.js $ - ambitus-$MAJOR.js break-$MAJOR.js capo-$MAJOR.js clair-$MAJOR.js clip-$MAJOR.js $ - combine-$MAJOR.js diag-$MAJOR.js equalbars-$MAJOR.js grid-$MAJOR.js grid2-$MAJOR.js $ - MIDI-$MAJOR.js page-$MAJOR.js perc-$MAJOR.js soloffs-$MAJOR.js sth-$MAJOR.js $ + abcweb-$MAJOR.js abcweb1-$MAJOR.js abcweb2-$MAJOR.js chord-$MAJOR.js $ + json-$MAJOR.js toaudio-$MAJOR.js snd-$MAJOR.js play-$MAJOR.js psvg-$MAJOR.js $ + 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 $ 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 # npm/fossil new release rule version command = npm version $out;$ - tag=`ag '"version"' package.json|cut -d'"' -f4`;$ + tag=`ag -Q '"version"' package.json|cut -d'"' -f4`;$ fossil commit -m "New release v$$tag" -tag v$$tag;$ $tool -v;$ npm publish 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-2018 Jean-Francois Moine +// Copyright (C) 2014-2020 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 @@ -17,19 +17,28 @@ // You should have received a copy of the GNU General Public License // along with abc2svg. If not, see . // user definitions var user = { - read_file: function(fn) { // include a file (%%abc-include) - var file = abc2svg.readFile(fn), + read_file: function(fn) { // read a file (main or included) + var i, + file = abc2svg.readFile(fn) + + if (!file) + return file i = file.indexOf('\r') + if (i >= 0) { + if (file[i + 1] == '\n') + file = file.replace(/\r\n/g, '\n') // M$ + else + fike = file.replace(/\r/g, '\n') // Mac + } + + // load the required modules (synchronous) + abc2svg.modules.load(file) - if (i < 0) - return file // standard - if (file[i + 1] == '\n') - return file.replace(/\r\n/g, '\n') // M$ - return file.replace(/\r/g, '\n') // Mac + return file }, errtxt: '' } // print or store the error messages @@ -56,13 +65,13 @@ file = user.read_file(fn) } catch (e) { } } } - if (!file && fn != "default.abc") { -// abc2svg.abort(new Error("Cannot read file '" + fn + "'")) - user.errmsg("Cannot read file '" + fn + "'") + if (!file) { + if (fn != "default.abc") + user.errmsg("Cannot read file '" + fn + "'") return } // if (typeof(utf_convert) == "function") // file = utf_convert(file) @@ -71,23 +80,61 @@ abc2svg.abort(new Error("No MEI support")); abc.mei2mus(file) return } - // load the required modules (synchronous) - abc2svg.modules.load(file) - // generate try { abc.tosvg(fn, file) } catch (e) { abc2svg.abort(e) } } // do_file() -function abc_cmd(cmd, args) { +function abc_cmd(cmd, args, interp_name) { var arg, parm, fn; + + abc2svg.abort = function(e) { + abc2svg.printErr('javascript error: ' + e.message + + '\nStack:\n' + e.stack) + abc2svg.quit() + } // abort() + + // put the last options before the last file + function arg_reorder(a) { + var f, + i = a.length - 2 + + while (i > 2 && a[i].slice(0, 2) == '--') + i -= 2 + f = a[--i] + a.splice(i, 1) + a.push(f) + } // arg_reorder() + + // if the first argument is a javascript file, load it + if (/\.js$/.test(args[0])) { + abc2svg.loadjs(args[0]) + args.shift() + } + + if (!args[0]) { + abc2svg.printErr('ABC translator with ' + interp_name + + ' and abc2svg-' + abc2svg.version + ' ' + + abc2svg.vdate + + '\nUsage:\n ' + cmd + + ' [script.js] [options] ABC_file [[options] ABC_file]* [options]\n\ +Arguments:\n\ + script.js generation script to load - default: tohtml.js (HTML+SVG)\n\ + options ABC options (the last options are moved before the last file)\n\ + ABC_file ABC file') + abc2svg.quit() + } + + // the default output is HTML+SVG + if (typeof abc2svg.abc_init != 'function') + abc2svg.loadjs("tohtml.js") // initialize the backend abc = new abc2svg.Abc(user) if (typeof global == "object" && !global.abc) global.abc = abc @@ -97,35 +144,31 @@ try { do_file("default.abc") } catch (e) { } + // put the last options before the last ABC file + if (args.length > 2 && args[args.length - 2].slice(0, 2) == '--') + arg_reorder(args) + while (1) { arg = args.shift() if (!arg) break - if (arg[0] == "-") { - if (arg[1] == "-") { - parm = args.shift(); - parm = arg.replace('--', 'I:') + " " + parm + "\n" - abc2svg.modules.load(parm); - abc.tosvg(cmd, parm) - } - } else { - if (fn) { - do_file(fn); - abc.tosvg('cmd', '%%select\n') - } - fn = arg - } - } - if (fn) - do_file(fn); - + if (arg[0] == "-" && arg[1] == "-") { + parm = arg.replace('--', 'I:') + " " + + args.shift() + "\n" + abc2svg.modules.load(parm) + abc.tosvg(cmd, parm) + } else { + do_file(arg) + abc.tosvg('cmd', '%%select\n') + } + } abc2svg.abc_end() } // nodejs if (typeof module == 'object' && typeof exports == 'object') { exports.user = user; exports.abc_cmd = abc_cmd } Index: core/abc2svg.js ================================================================== --- core/abc2svg.js +++ core/abc2svg.js @@ -1,8 +1,8 @@ // abc2svg - abc2svg.js // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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,10 +55,16 @@ SL_BELOW: 0x02, SL_AUTO: 0x03, SL_HIDDEN: 0x04, SL_DOTTED: 0x08 // (modifier bit) }; + +// !! tied to the symbol types in abc2svg.js !! +abc2svg.sym_name = ['bar', 'clef', 'custos', '', 'grace', + 'key', 'meter', 'Zrest', 'note', 'part', + 'rest', 'yspace', 'staves', 'Break', 'tempo', + '', 'block', 'remark'] // key table - index = number of accidentals + 7 abc2svg.keys = [ new Int8Array([-1,-1,-1,-1,-1,-1,-1 ]), // 7 flat signs new Int8Array([-1,-1,-1, 0,-1,-1,-1 ]), // 6 flat signs @@ -139,10 +145,60 @@ return abc2svg.b40_a[b % 40] } // b40a() abc2svg.b40m = function(b) { return ((b / 40) | 0) * 12 + abc2svg.b40_m[b % 40] } // b40m() + +// chord table +// This table is used in various modules +// to convert the types of chord symbols to a minimum set. +// More chord types may be added by the command %%chordalias. +abc2svg.ch_alias = { + "maj": "", + "min": "m", + "-": "m", + "°": "dim", + "+": "aug", + "+5": "aug", + "maj7": "M7", + "Δ7": "M7", + "Δ": "M7", + "min7": "m7", + "-7": "m7", + "ø7": "m7b5", + "°7": "dim7", + "min+7": "m+7", + "aug7": "+7", + "7+5": "+7", + "7#5": "+7", + "sus": "sus4", + "7sus": "7sus4" +} // ch_alias + +// simplify a rational number n/d +abc2svg.rat = function(n, d) { + var a, t, + n0 = 0, + d1 = 0, + n1 = 1, + d0 = 1 + while (1) { + if (d == 0) + break + t = d + a = (n / d) | 0 + d = n % d + n = t + t = n0 + a * n1 + n0 = n1 + n1 = t + t = d0 + a * d1 + d0 = d1 + d1 = t + } + return [n1, d1] +} // rat() // compare pitches // This function is used to sort the note pitches abc2svg.pitcmp = function(n1, n2) { return n1.pit - n2.pit } @@ -182,10 +238,11 @@ // error texts var errs = { bad_char: "Bad character '$1'", bad_grace: "Bad character in grace note sequence", + bad_transp: "Bad transpose value", bad_val: "Bad value in $1", bar_grace: "Cannot have a bar in grace notes", ignored: "$1: inside tune - ignored", misplaced: "Misplaced '$1' in %%score", must_note: "!$1! must be on a note", @@ -193,20 +250,21 @@ nonote_vo: "No note in voice overlay", not_ascii: "Not an ASCII character", not_enough_n: 'Not enough notes/rests for %%repeat', not_enough_m: 'Not enough measures for %%repeat', not_enough_p: "Not enough parameters in %%map", - not_in_tune: "Cannot have '$1' inside a tune" + not_in_tune: "Cannot have '$1' inside a tune", + notransp: "Cannot transpose with a temperament" } var self = this, // needed for modules glovar = { meter: { type: C.METER, // meter in tune header wmeasure: 1, // no M: a_meter: [] // default: none - } + }, }, info = {}, // information fields parse = { ctx: {}, prefix: '%', @@ -222,11 +280,11 @@ if (!obj) return obj var tmp = new obj.constructor for (var k in obj) if (obj.hasOwnProperty(k)) { - if (lvl && typeof obj[k] == 'object') + if (lvl && typeof obj[k] != "number") tmp[k] = clone(obj[k], lvl - 1) else tmp[k] = obj[k] } return tmp @@ -273,10 +331,15 @@ function error(sev, s, msg, a1, a2, a3, a4) { var i, j, regex, tmp if (!sev && cfmt.quiet) return + if (s) { + if (s.err) // only one error message per symbol + return + s.err = true + } if (user.textrans) { tmp = user.textrans[msg] if (tmp) msg = tmp } @@ -327,9 +390,9 @@ } // inject javascript code function js_inject(js) { if (!/eval *\(|Function|setTimeout|setInterval/.test(js)) - eval('"use strict"\n' + js) + eval('"use strict";\n' + js) else syntax(1, "Unsecure code") } Index: core/deco.js ================================================================== --- core/deco.js +++ core/deco.js @@ -1,8 +1,8 @@ // abc2svg - deco.js - decorations // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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 @@ -14,10 +14,44 @@ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with abc2svg-core. If not, see . + +// Decoration objects +// dd { // decoration definition (static) +// dd_en, // definition of the ending decoration +// dd_st, // definition of the starting decoration +// func, // function +// glyph, // glyph +// h, // height / ascent +// hd, // descent +// inv, // inverted glyph +// name, // name +// str, // string +// wl, // left width +// wr, // right width +// } +// de { // decoration elements (in an array - one list per music line) +// dd, // definition of the decoration +// defl { // flags +// noen, // no end of this decoration +// nost, // no start of this decoration +// }, +// has_val, // defined value +// ix, // index of the decoration in the 'de' list +// lden, // end of a long decoration +// ldst, // start of a long decoration if true +// m, // note index when note decoration +// prev, // previous decoration (hack for 'tr~~~~~') +// s, // symbol +// start, // start of the decoration (in the ending element) +// up, // above the symbol +// val, // value +// x, // x offset +// y, // y offset +// } var dd_tb = {}, // definition of the decorations a_de // array of the decoration elements // decorations - populate with standard decorations @@ -70,12 +104,12 @@ "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", - "trill(": "3 ltr 8 0 0", - "trill)": "3 ltr 8 0 0", + "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", @@ -86,12 +120,12 @@ pppp: "6 pppp 18 10 25", pralltriller: "3 umrd 10 5 5", sfz: "6 sfz 18 4 10", ped: "4 ped 18 6 10", "ped-up": "4 pedoff 12 4 4", - "ped(": "4 lped 20 1 1", - "ped)": "4 lped 20 1 1", + "ped(": "4 lped 20 5 5", + "ped)": "4 lped 20 5 5", "crescendo(": "6 cresc 18 0 0", "crescendo)": "6 cresc 18 0 0", "<(": "6 cresc 18 0 0", "<)": "6 cresc 18 0 0", "diminuendo(": "6 dim 18 0 0", @@ -126,11 +160,19 @@ "//": "38 0 0 6 6", "///": "38 0 0 6 6", "beam-accel": "39 0 0 0 0", "beam-rall": "39 0 0 0 0", stemless: "40 0 0 0 0", - rbend: "41 0 0 0 0"}, + rbend: "41 0 0 0 0", + editorial: "42 0 0 0 0", + "sacc-1": "3 sacc-1 6,4 4 4", + 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"}, // 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] @@ -351,11 +393,11 @@ y += 3; y = (((y + 6) / 6) | 0) * 6 - 6 /* between lines */ } if (up) s.ymx = y + dd.h - else if (dd.name[0] == 'w') + else if (dd.name[0] == 'w') // wedge s.ymn = y - dd.h else s.ymn = y de.y = y if (s.type == C.NOTE) @@ -447,18 +489,21 @@ function d_trill(de) { if (de.ldst) return var up, y, w, tmp, dd = de.dd, + de2 = de.prev, s2 = de.s, st = s2.st, s = de.start.s, x = s.x - if (de.prev) { // hack 'tr~~~~~' - x = de.prev.x + 10; - y = de.prev.y + if (de2) { // same height + x = de2.s.x + de.dd.wl + 2 + de2.val -= de2.dd.wr + if (de2.val < 8) + de2.val = 8 } de.st = st if (dd.func != 4) { // if not below switch (dd.glyph) { @@ -479,28 +524,43 @@ } } else { w = s2.x - x - 6 if (s2.type == C.NOTE) w -= 6 - if (w < 20) { - x -= (20 - w) * .5; - w = 20 + if (w < 10) { + x -= 10 - w + w = 10 } } dd = de.dd; - if (!y) - y = y_get(st, up, x, w) + y = y_get(st, up, x - dd.wl - 5, w) if (up) { tmp = staff_tb[s.st].topbar + 2 if (y < tmp) y = tmp } else { - y -= dd.h; tmp = staff_tb[s.st].botbar - 2 if (y > tmp) y = tmp + y -= dd.h + } + if (de2) { // if same height + if (up) { + if (y < de2.y) + y = de2.y // (only on one note) + } else { + if (y >= de2.y) { + y = de2.y + } else { + do { + de2.y = y + de2 = de2.prev // go backwards + } while (de2) + } + } } + de.lden = false; de.has_val = true; de.val = w; de.x = x; de.y = y @@ -524,11 +584,12 @@ return } var yc, up, inv, s = de.s, dd = de.dd, - x = s.x, +// x = s.x, + 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) @@ -585,21 +646,21 @@ 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 + 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) - 2 + 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) + y_set(s.st, false, s.x - dd.wl, w, yc - dd.hd) if (dd.name == "fermata") inv = true; s.ymn = yc } break @@ -625,56 +686,60 @@ ] // add a decoration /* syntax: * %%deco [] + * "" may be followed by "," (descent) */ function deco_add(param) { 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, + var a, dd, dd2, name2, c, i, elts, str, hd, text = decos[nm] if (!text) { if (cfmt.decoerr) error(1, null, "Unknown decoration '$1'", nm) return //undefined } // extract the values - a = text.match(/(\d+)\s+(.+?)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)/) + a = text.match(/(\d+)\s+(.+?)\s+([0-9.,]+)\s+([0-9.]+)\s+([0-9.]+)/) if (!a) { error(1, null, "Invalid decoration '$1'", nm) return //undefined } var c_func = Number(a[1]), // glyph = a[2], - h = parseFloat(a[3]), + h = a[3], wl = parseFloat(a[4]), wr = parseFloat(a[5]) if (isNaN(c_func)) { error(1, null, "%%deco: bad C function value '$1'", a[1]) return //undefined } - if ((c_func < 0 || c_func > 10) - && (c_func < 32 || c_func > 41)) { + if (c_func > 10 + && (c_func < 32 || c_func > 43)) { 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 (h < 0 || wl < 0 || wr < 0) { - error(1, null, "%%deco: cannot have a negative value '$1'", text) - return //undefined + if (h.indexOf(',') > 0) { + h = h.split(',') + hd = h[1] + h = h[0] + } else { + hd = 0 } if (h > 50 || wl > 80 || wr > 80) { error(1, null, "%%deco: abnormal h/wl/wr value '$1'", text) return //undefined } @@ -689,11 +754,12 @@ } /* set the values */ dd.func = dd.name.indexOf("head-") == 0 ? 9 : c_func; dd.glyph = a[2]; - dd.h = h; + 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] == '"') @@ -827,11 +893,11 @@ for (j = 0; j <= prev.nhd; j++) prev.notes[j].dur *= 2 continue case 35: /* xstem */ if (s.type != C.NOTE) { - error(1, s, "!xstem! must be on a note") + error(1, s, errs.must_note, dd.name) continue } s.xstem = true; continue case 36: /* beambr1 / beambr2 */ @@ -866,10 +932,33 @@ s.stemless = true continue case 41: /* rbend */ s.rbstop = 2 // with end continue + case 42: // editorial + if (!s.notes[0].acc) + continue + dcn = "sacc" + s.notes[0].acc.toString() // small accidental + dd = dd_tb[dcn] + if (!dd) { + dd = deco_def(dcn) + if (!dd) { + error(1, s, errs.bad_val, "!editorial!") + continue + } + } + delete s.notes[0].acc + curvoice.acc[s.notes[0].pit + 19] = 0 // ignore the accidental + 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) + continue } // add the decoration in the symbol if (!s.a_dd) s.a_dd = [] @@ -926,12 +1015,12 @@ return wl } /* -- draw the decorations -- */ /* (the staves are defined) */ -function draw_all_deco() { - if (a_de.length == 0) +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, new_de = [], ymid = [] @@ -975,17 +1064,17 @@ st = de.st; if (!staff_tb[st].topbar) continue // invisible staff x = de.x; -// y = de.y + staff_tb[st].y / staff_tb[st].staffscale y = de.y + staff_tb[st].y // update the coordinates if head decoration if (de.m != undefined) { note = s.notes[de.m]; - x += note.shhd * stv_g.scale; + 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 && ((de.up && st > 0) @@ -1131,10 +1220,12 @@ defl: {}, x: s.x, y: s.y, // dy: 0 } + if (s.type == C.BAR) + de.x -= s.wl / 2 - 2 a_de.push(de) if (dd.dd_en) { de.ldst = true } else if (dd.dd_st) { //fixme: pb with "()" @@ -1212,16 +1303,14 @@ } } // create_dh() // create all decorations of a note (chord and heads) function create_all(s) { - var m - if (s.a_dd) create_deco(s) if (s.notes) { - for (m = 0; m < s.notes.length; m++) { + for (var m = 0; m < s.notes.length; m++) { if (s.notes[m].a_dcn) create_dh(s, m) } } } // create_all() @@ -1255,16 +1344,16 @@ break } } if (j == n_de) { // no end, insert one de2 = { - s: de.s, + s: s, st: de.st, dd: dd2, ix: a_de.length - 1, x: realwidth - 6, - y: de.s.y, + y: s.y, lden: true, defl: { noen: true } } @@ -1275,13 +1364,15 @@ a_de.push(de2) } de2.start = de; de2.defl.nost = de.defl.nost - // handle 'tr~~~~~' - if (dd.name == "trill(" - && i > 0 && a_de[i - 1].dd.name == "trill") + // handle same decoration type at a same time + if (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)) de2.prev = a_de[i - 1] } // add starting decorations for (i = 0; i < n_de; i++) { @@ -1315,11 +1406,11 @@ case C.METER: continue } break } - if (a_de.length != 0) + if (a_de.length) ldeco_update(s) for ( ; s; s = s.ts_next) { switch (s.type) { case C.BAR: @@ -1329,10 +1420,11 @@ case C.SPACE: break case C.GRACE: for (g = s.extra; g; g = g.next) create_all(g) + break default: continue } create_all(s) } @@ -1361,18 +1453,17 @@ // - chord symbols // - repeat brackets /* (the staves are not yet defined) */ /* (unscaled delayed output) */ function draw_deco_staff() { - var s, first_gchord, p_voice, x, y, w, i, v, de, dd, - gch, gch2, ix, top, bot, - minmax = new Array(nstaff + 1), - nd = a_de.length + var s, p_voice, y, i, v, de, dd, + minmax = new Array(nstaff + 1), + nd = a_de.length /* draw the repeat brackets */ function draw_repbra(p_voice) { - var s, s1, y, y2, i, p, w, wh, first_repeat; + var s, s1, x, y, y2, i, p, w, wh, first_repeat; /* search the max y offset */ 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) @@ -1399,12 +1490,12 @@ y = y2 /* have room for the repeat numbers */ if (s1.text) { wh = strwh(s1.text); - y2 = y_get(p_voice.st, true, s1.x + 4, wh[0]); - y2 += wh[1] + y2 = y_get(p_voice.st, true, s1.x + 4, wh[0]) + + wh[1] if (y < y2) y = y2 } if (s.rbstart) s = s.prev @@ -1428,12 +1519,13 @@ break } if (s1 == s) break x = s1.x -// if (s1.bar_type[0] == ":") -// x -= 4; + if (cfmt.measurenb > 0 & s.bar_num + && s.bar_num % cfmt.measurenb) + x += 6 if (s.type != C.BAR) { w = s.rbstop ? 0 : s.x - realwidth + 4 } else if ((s.bar_type.length > 1 // if complex bar && s.bar_type != "[]") || s.bar_type == "]") { @@ -1531,61 +1623,11 @@ if (de.up) y += dd.h; y_set(de.st, de.up, de.x, de.val, y) } - // search the vertical offset for the chord symbols - for (i = 0; i <= nstaff; i++) - minmax[i] = { - ymin: 0, - ymax: 24 - } - for (s = tsfirst; s; s = s.ts_next) { - if (!s.a_gch) - continue - if (!first_gchord) - first_gchord = s; - gch2 = null - for (ix = 0; ix < s.a_gch.length; ix++) { - gch = s.a_gch[ix] - if (gch.type != 'g') - continue - gch2 = gch // chord closest to the staff - if (gch.y < 0) - break - } - if (gch2) { - w = gch2.wh[0] - if (gch2.y >= 0) { - y = y_get(s.st, true, s.x, w) - if (y > minmax[s.st].ymax) - minmax[s.st].ymax = y - } else { - y = y_get(s.st, false, s.x, w) - if (y < minmax[s.st].ymin) - minmax[s.st].ymin = y - } - } - } - - // draw the chord symbols if any - if (first_gchord) { - for (i = 0; i <= nstaff; i++) { - bot = staff_tb[i].botbar; - if (minmax[i].ymin > bot - 4) - minmax[i].ymin = bot - 4 - top = staff_tb[i].topbar; - if (minmax[i].ymax < top + 4) - minmax[i].ymax = top + 4 - } - set_dscale(-1) /* restore the scale parameters */ - for (s = first_gchord; s; s = s.ts_next) { - if (!s.a_gch) - continue - self.draw_gchord(s, minmax[s.st].ymin, minmax[s.st].ymax) - } - } + draw_all_chsy() // draw all chord symbols /* draw the repeat brackets */ for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] if (p_voice.second || !p_voice.sym) @@ -1629,14 +1671,13 @@ xy_str(0, y, bar_num.toString()); y_set(st, true, 0, 20, y + gene.curfont.size + 2) } else if (bar_num % cfmt.measurenb == 0) { for ( ; ; s = s.ts_next) { switch (s.type) { - case C.TIMESIG: case C.CLEF: - case C.KEYSIG: - case C.FMTCHG: + case C.KEY: + case C.METER: case C.STBRK: continue } break } @@ -1643,29 +1684,25 @@ // don't display the number twice if (s.type != C.BAR || !s.bar_num) { if (s.prev) s = s.prev; - x = s.x + s.wr; any_nb = true; w = w0 if (bar_num >= 10) w *= bar_num >= 100 ? 3 : 2 - if (gene.curfont.box) - w += 4; + if (gene.curfont.pad) + w += gene.curfont.pad * 2 + x = s.x + s.wr + 1 // 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; + y += 2 + gene.curfont.pad xy_str(x, y, bar_num.toString()) - if (gene.curfont.box) { - y += 2; - w += 3 - } - y += gene.curfont.size; + y += gene.curfont.size + gene.curfont.pad y_set(st, true, x, w, y); - s.ymx = y +// s.ymx = y } } } for ( ; s; s = s.ts_next) { @@ -1695,11 +1732,13 @@ if (!any_nb) any_nb = true; w = w0 if (bar_num >= 10) w *= bar_num >= 100 ? 3 : 2 - x = s.x - w * .4; + if (gene.curfont.pad) + w += gene.curfont.pad * 2 + x = s.x - w * (s.text ? .6 : .4) y = y_get(st, true, x, w) if (y < staff_tb[st].topbar + 3) y = staff_tb[st].topbar + 3 if (s.next.type == C.NOTE) { if (s.next.stem > 0) { @@ -1708,21 +1747,15 @@ } else { if (y < s.next.y) y = s.next.y } } - y += 2; - if (gene.curfont.box) { - y += 2; - w += 3 - } + y += 2 + gene.curfont.pad xy_str(x, y, bar_num.toString()) - y += gene.curfont.size; - if (gene.curfont.box) - y += 2; + y += gene.curfont.size + gene.curfont.pad y_set(st, true, x, w, y); - s.ymx = y +// s.ymx = y } gene.nbar = bar_num if (font_size) param_set_font("measurefont", "* " + font_size.toString()); @@ -1729,22 +1762,22 @@ } /* -- 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, - dy = 0, /* put the tempo indication at top */ - ht = 0 + var s, 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, dosh = 0, shift = 1, x = -100 // (must be negative for %%soloffs) for (s = tsfirst; s; s = s.ts_next) { - if (s.type != C.TEMPO || s.del) + if (s.type != C.TEMPO || s.invis) continue if (!some_tempo) some_tempo = s; w = s.tempo_wh[0] if (s.time == 0 && s.x > 40) // at start of tune and no %%soloffs, @@ -1769,11 +1802,11 @@ dy = ymin + ht - top /* draw the tempo indications */ for (s = some_tempo; s; s = s.ts_next) { if (s.type != C.TEMPO - || s.del) // (displayed by %%titleformat) + || s.invis) // (displayed by %%titleformat) continue if (user.anno_start || user.anno_stop) { s.wl = 16; s.wr = 30; s.ymn = (dosh & 1) ? h : y; @@ -1781,39 +1814,39 @@ anno_start(s) } writempo(s, s.x - 16, (dosh & 1) ? h : y); anno_stop(s); dosh >>= 1 + while (s.ts_next && s.ts_next.type == C.TEMPO) + s = s.ts_next } } /* then, put the parts */ /*fixme: should reduce vertical space if parts don't overlap tempo...*/ ymin = staff_tb[st].topbar + 6 for (s = tsfirst; s; s = s.ts_next) { - if (s.type != C.PART) + if (s.type != C.PART || s.invis) continue if (!some_part) { some_part = s; set_font("parts"); - h = gene.curfont.size + 2 - /* + cfmt.partsspace ?? */ + 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 (ymin < y) ymin = y } if (some_part) { set_sscale(-1) - if (gene.curfont.box) - h += 2 - if (top < ymin + h + ht) + if (top + dy < ymin + h + ht) dy = ymin + h + ht - top for (s = some_part; s; s = s.ts_next) { - if (s.type != C.PART) + 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; @@ -1822,9 +1855,11 @@ 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 } Index: core/draw.js ================================================================== --- core/draw.js +++ core/draw.js @@ -1,8 +1,8 @@ // abc2svg - draw.js - draw functions // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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 @@ -32,11 +32,12 @@ BEAM_STUB = 8, /* length of stub for flag under beam */ SLUR_SLOPE = .5, /* max slope of a slur */ GSTEM = 15, /* grace note stem length */ GSTEM_XOFF = 2.3 /* x offset for grace note stem */ - var cache + var cache, + anno_a = [] // symbols with annotations /* -- compute the best vertical offset for the beams -- */ function b_pos(grace, stem, nflags, b) { var top, bot, d1, d2, shift = !grace ? BEAM_SHIFT : 3.5, @@ -74,10 +75,11 @@ delete s.extra; delete s.text delete s.a_gch delete s.a_ly 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 } @@ -475,13 +477,12 @@ } } y1 = bm.a * x1 + bm.b - dy; x2 -= x1; - //--fixme: scale (bm.a already scaled!) x2 /= stv_g.scale; - dy2 = bm.a * x2 * stv_g.scale; + dy2 = bm.a * x2 xypath(x1, y1, true); output += 'l' + x2.toFixed(1) + ' ' + (-dy2).toFixed(1) + 'v' + h.toFixed(1) + 'l' + (-x2).toFixed(1) + ' ' + dy2.toFixed(1) + 'z"/>\n' @@ -625,11 +626,11 @@ /* -- draw the left side of the staves -- */ function draw_lstaff(x) { // if (cfmt.alignbars) // return - var i, j, yb, h, + var i, j, yb, h, fl, nst = cur_sy.nstaff, l = 0 /* -- draw a system brace or bracket -- */ function draw_sysbra(x, st, flag) { @@ -657,15 +658,16 @@ else out_bracket(x, yt, yt - yb) } for (i = 0; ; i++) { - if (cur_sy.staves[i].flags & (OPEN_BRACE | OPEN_BRACKET)) + fl = cur_sy.staves[i].flags + if (fl & (OPEN_BRACE | OPEN_BRACKET)) l++ if (cur_sy.st_print[i]) break - if (cur_sy.staves[i].flags & (CLOSE_BRACE | CLOSE_BRACKET)) + if (fl & (CLOSE_BRACE | CLOSE_BRACKET)) l-- if (i == nst) break } for (j = nst; j > i; j--) { @@ -677,26 +679,27 @@ yb = staff_tb[j].y + staff_tb[j].botbar * staff_tb[j].staffscale; h = staff_tb[i].y + staff_tb[i].topbar * staff_tb[i].staffscale - yb; xypath(x, yb); output += "v" + (-h).toFixed(1) + '"/>\n' for (i = 0; i <= nst; i++) { - if (cur_sy.staves[i].flags & OPEN_BRACE) + fl = cur_sy.staves[i].flags + if (fl & OPEN_BRACE) draw_sysbra(x, i, CLOSE_BRACE) - if (cur_sy.staves[i].flags & OPEN_BRACKET) + if (fl & OPEN_BRACKET) draw_sysbra(x, i, CLOSE_BRACKET) - if (cur_sy.staves[i].flags & OPEN_BRACE2) + if (fl & OPEN_BRACE2) draw_sysbra(x - 6, i, CLOSE_BRACE2) - if (cur_sy.staves[i].flags & OPEN_BRACKET2) + if (fl & OPEN_BRACKET2) draw_sysbra(x - 6, i, CLOSE_BRACKET2) } } /* -- draw the time signature -- */ -function draw_meter(x, s) { +function draw_meter(s) { if (!s.a_meter) return - var dx, i, j, meter, + var dx, i, j, meter, x, st = s.st, p_staff = staff_tb[st], y = p_staff.y; // adjust the vertical offset according to the staff definition @@ -719,80 +722,133 @@ x, y + 12, m_gl(meter.top)) } } } + var acc_nd = {} // cache of the microtonal accidentals + /* -- draw an accidental -- */ -function draw_acc(x, y, acc, - micro_n, - micro_d) { - if (micro_n) { - if (micro_n == micro_d) { - acc = acc == -1 ? // flat - -2 : 2 // double flat : sharp - } else if (micro_n * 2 != micro_d) { - xygl(x, y, "acc" + acc + '_' + micro_n + '_' + micro_d) - return - } - } - xygl(x, y, "acc" + acc) -} +function draw_acc(x, y, a) { + if (typeof a != "number") { // if microtone + var c, + n = a[0], + d = a[1] + + c = n + '_' + d + a = acc_nd[c] + if (!a) { + a = abc2svg.rat(Math.abs(n), d) + d = a[1] + a = (n < 0 ? -a[0] : a[0]).toString() + if (d != 1) + a += '_' + d + acc_nd[c] = a + } + } + xygl(x, y, "acc" + a) +} + +// memorize the helper/ledger lines +function set_hl(p_st, n, x, dx1, dx2) { + var i, hl + + if (n >= 0) { + hl = p_st.hlu[n] + if (!hl) + hl = p_st.hlu[n] = [] + } else { + hl = p_st.hld[-n] + if (!hl) + hl = p_st.hld[-n] = [] + } + + for (i = 0; i < hl.length; i++) { + if (x >= hl[i][0]) + break + } + if (i == hl.length) { + hl.push([x, dx1, dx2]) + } else if (x > hl[i][0]) { + hl.splice(++i, 0, [x, dx1, dx2]) + } else { + if (dx1 < hl[i][1]) + hl[i][1] = dx1 + if (dx2 > hl[i][2]) + hl[i][2] = dx2 + } +} // set_hl() // draw helper lines -//fixme: double lines when needed for different voices // (possible hook) -Abc.prototype.draw_hl = function(x, s, hltype) { - var i, j, n, +Abc.prototype.draw_hl = function(s) { + var i, j, n, note, hla = [], st = s.st, p_staff = staff_tb[st] // check if any helper line if (!p_staff.hll) return // no helper line (no line) for (i = 0; i <= s.nhd; i++) { - if (!p_staff.hlmap[s.notes[i].pit - p_staff.hll]) - hla.push((s.notes[i].pit - 18) * 3) + note = s.notes[i] + if (!p_staff.hlmap[note.pit - p_staff.hll]) + hla.push([note.pit - 18, + note.shhd * stv_g.scale]) } n = hla.length if (!n) return // no // handle the helper lines out of the staff - var staffb = p_staff.y, + var dx1, dx2, hl, shhd,hlp, stafflines = p_staff.stafflines, - top = (stafflines.length - 1) * 6, - bot = p_staff.botline, + top = stafflines.length - 1, + yu = top, + bot = p_staff.botline / 6, yl = bot, - yu = top + dx = s.grace ? 4 : hw_tb[s.head] * 1.3 + + // get the x start and x stop of the intermediate helper lines + note = s.notes[s.stem < 0 ? s.nhd : 0] + shhd = note.shhd for (i = 0; i < hla.length; i++) { - if (hla[i] < yl) { - yl = (((hla[i] + 51) / 6) | 0) * 6 - 48; + hlp = hla[i][0] + dx1 = (hla[i][1] < shhd ? hla[i][1] : shhd) - dx + dx2 = (hla[i][1] > shhd ? hla[i][1] : shhd) + dx + if (hlp < bot * 2) { + if (hlp < yl * 2) + yl = ++hlp >> 1 n-- - } else if (hla[i] > yu) { - yu = ((hla[i] / 6) | 0) * 6; + } else if (hlp > top * 2) { + yu = hlp >> 1 n-- } + set_hl(p_staff, hlp >> 1, s.x, dx1, dx2) } - for (; yl < bot; yl += 6) - xygl(x, staffb + yl, hltype) - for (; yu > top; yu -= 6) - xygl(x, staffb + yu, hltype) + + dx1 = shhd - dx + dx2 = shhd + dx + while (++yl < bot) + set_hl(p_staff, yl, + s.x, dx1, dx2) + while (--yu > top) + set_hl(p_staff, yu, + s.x, dx1, dx2) if (!n) return // no more helper lines // draw the helper lines inside the staff i = yl; j = yu - while (i > bot && stafflines[i / 6] == '-') - i -= 6 - while (j < top && stafflines[j / 6] == '-') - j += 6 - for ( ; i < j; i += 6) { - if (stafflines[i / 6] == '-') - xygl(x, staffb + i, hltype) // hole + while (i > bot && stafflines[i] == '-') + i-- + while (j < top && stafflines[j] == '-') + j++ + for ( ; i < j; i++) { + if (stafflines[i] == '-') + set_hl(p_staff, i, s.x, dx1, dx2) } } /* -- draw a key signature -- */ // (possible hook) @@ -909,15 +965,15 @@ || 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(x, s2, "hl"); + self.draw_hl(s2) last_shift = shift; - draw_acc(x, staffb + shift, - acc.acc, acc.micro_n, acc.micro_d); + draw_acc(x, staffb + shift, acc.acc) x += 5.5 } } } @@ -938,10 +994,21 @@ 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) + } else { + xygl(x - 10, y, "mtr" + ((n / 10) | 0)) + xygl(x - 2, y, "mtr" + (n % 10)) + } +} // nrep_out() /* -- draw a rest -- */ /* (the staves are defined) */ var rest_tb = [ "r128", "r64", "r32", "r16", "r8", @@ -964,18 +1031,19 @@ s.nflags = -4; s.dots = 0; /* don't use next/prev: there is no bar in voice overlay */ s2 = s.ts_next - while (s2 && s2.time != s.time + s.dur) - s2 = s2.ts_next; - x = s2 ? s2.x : realwidth; + 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) / 2 + x = (x + s2.x + s2.wr) / 2 /* center the associated decorations */ if (s.a_dd) deco_update(s, x - s.x); s.x = x @@ -998,23 +1066,16 @@ yb += (p_staff.topbar + p_staff.botbar) / 2 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) { - set_font("annotation"); - if (gene.curfont.box) { - gene.curfont.box = false; - bx = true - } - xy_str(x, yb + p_staff.topbar - 9, - s.rep_nb.toString(), "c") - if (bx) - gene.curfont.box = true - } - } - anno_stop(s) + if (s.rep_nb > 2 && s.v == cur_sy.top_voice + && cfmt.measrepnb > 0 + && !(s.rep_nb % cfmt.measrepnb)) + nrep_out(x, yb + p_staff.topbar, s.rep_nb) + } + anno_a.push(s) return } set_scale(s); anno_start(s); @@ -1028,11 +1089,12 @@ if (i == 7 && y == 12 && p_staff.stafflines.length <= 2) y -= 6 /* semibreve a bit lower */ // draw the rest - xygl(x, y + yb, s.notes[0].head || rest_tb[i]) + 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) { @@ -1040,11 +1102,11 @@ switch (p_staff.stafflines[j + 1]) { case '|': case '[': break default: - xygl(x, y + 6 + yb, "hl1") + set_hl(p_staff, j + 1, x, -7, 7) break } if (i == 9) { /* longa */ y -= 6; j-- @@ -1059,24 +1121,26 @@ switch (p_staff.stafflines[j]) { case '|': case '[': break default: - xygl(x, y + yb, "hl1") + set_hl(p_staff, j, x, -7, 7) break } } if (s.dots) { x += 8; y += yb + 3 - for (i = 0; i < s.dots; i++) { - xygl(x, y, "dot"); + j = s.dots + i = (s.dur_orig / 12) >> ((5 - s.nflags) - j) + while (j-- > 0) { + xygl(x, y, (i & (1 << j)) ? "dot" : "dot+") x += 3.5 } } set_color(); - anno_stop(s) + anno_a.push(s) } /* -- draw grace notes -- */ /* (the staves are defined) */ function draw_gracenotes(s) { @@ -1093,11 +1157,11 @@ } anno_start(g); draw_note(g, !bm.s2) if (g == bm.s2) bm.s2 = null /* (draw flags again) */ - anno_stop(g) + anno_a.push(s) if (g.sls || g.sl2) slur = true if (!g.next) break /* (keep the last note) */ } @@ -1121,11 +1185,11 @@ x1, y1, g.stem > 0) } /* slur */ //fixme: have a full key symbol in voice - if (s.p_v.key.k_bagpipe /* no slur when bagpipe */ + 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) @@ -1283,33 +1347,15 @@ // /* special case for voice unison */ // if (s.nohdi1 != undefined // && m >= s.nohdi1 && m < s.nohdi2) // return - var elts = identify_note(s, note.dur), + var elts = identify_note(s, note.dur), head = elts[0], dots = elts[1], nflags = elts[2] - /* output a ledger line if horizontal shift / chord - * and note on a line */ - if (y % 6 == 0 - && shhd != (s.stem > 0 ? s.notes[0].shhd : s.notes[s.nhd].shhd)) { - yy = 0 - if (y >= 30) { - yy = y - if (yy % 6) - yy -= 3 - } else if (y <= -6) { - yy = y - if (yy % 6) - yy += 3 - } - if (yy) - xygl(x_note, yy + staffb, "hl") - } - /* draw the head */ if (note.invis) { ; } else if (s.grace) { // don't apply %%map to grace notes p = "ghd"; @@ -1341,18 +1387,19 @@ // fall thru case C.SQUARE: if (nflags > -4) { p = "breve" } else { - p = "longa" + p = "longa" inv = s.stem > 0 } /* don't display dots on last note of the tune */ if (!tsnext && s.next && s.next.type == C.BAR && !s.next.next) dots = 0 + x_note += 1 break case C.EMPTY: p = "Hd" // white note break default: // black note @@ -1392,15 +1439,14 @@ /* draw the accidental */ if (note.acc) { x -= note.shac * stv_g.scale if (!s.grace) { - draw_acc(x, y + staffb, - note.acc, note.micro_n, note.micro_d) + draw_acc(x, y + staffb, note.acc) } else { g_open(x, y + staffb, 0, .75); - draw_acc(0, 0, note.acc, note.micro_n, note.micro_d); + draw_acc(0, 0, note.acc) g_close() } } if (old_color != false) set_color(old_color) @@ -1408,40 +1454,22 @@ /* -- 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, hltype, nflags, - x, y, note, x_hl, - y_tb = new Array(s.nhd + 1) + var s2, i, m, y, staffb, slen, c, nflags, + x, y, note, + y_tb = new Array(s.nhd + 1) if (s.dots) setdoty(s, y_tb) note = s.notes[s.stem < 0 ? s.nhd : 0]; // master note head - x_hl = x = x_head(s, note); + x = x_head(s, note) staffb = staff_tb[s.st].y - /* output the ledger lines */ - if (s.grace) { - hltype = "ghl"; - x_hl += 1.5 - } else { - hltype = "hl" - switch (s.head) { - case C.OVALBARS: - case C.OVAL: - x_hl -= .3; - hltype = "hl1" - break - case C.SQUARE: - x_hl -= 2; - hltype = "hl1" - break - } - } - self.draw_hl(x_hl, s, hltype) + self.draw_hl(s) /* draw the stem and flags */ y = y_head(s, note) if (!s.stemless) { slen = s.ys - s.y; @@ -1599,19 +1627,19 @@ var xx2 = mx + alfa * (x2 - mx), yy2 = my + alfa * (y2 - my) + height; xx2 = x2 + beta * (xx2 - x2); yy2 = y2 + beta * (yy2 - y2); - dx = .03 * (x2 - x1); -// if (dx > 10.) -// dx = 10. // dy = 1.6 * dir dy = 2 * dir; - dz = .2 + .001 * (x2 - x1) + dz = .2 + .001 * dx if (dz > .6) dz = .6; dz *= dir + dx *= .03 +// if (dx > 10.) +// dx = 10. // var scale_y = stv_g.st < 0 ? stv_g.scale : 1 var scale_y = 1 // (see set_dscale()) if (!dotted) output += '\n' } // draw a slur between two chords / notes /* (the staves are not yet defined) */ @@ -1961,11 +1989,12 @@ k.ymn = y if (i == i2 - 1) { dx = x2 if (k2.sl1) dx -= 5; - y -= height / 3 + if (i) + y -= height / 3 } else { dx = path[i + 1].x } if (i != 0) x1 = k.x @@ -2008,21 +2037,24 @@ // set the slur position switch (sl.ty & 0x07) { case C.SL_ABOVE: dir = 1; break case C.SL_BELOW: dir = -1; break default: - if (s.v != s2.v) - dir = -1 // always above ? - else - dir = slur_direction(s, s2); - sl.ty &= ~0x07; + dir = s.v != s2.v ? + 1 : // always above ? + slur_direction(s, s2) + sl.ty &= ~0x07 sl.ty |= dir > 0 ? C.SL_ABOVE : C.SL_BELOW break } // build the path of the symbols under the slur - if (dir * // if slur on first voice + if (s.v == s2.v) { + v = s.v + } if (!cur_sy.voices[s.v] || !cur_sy.voices[s2.v]) { + v = s.v > s2.v ? s.v : s2.v + } else if (dir * // if slur on first voice (cur_sy.voices[s.v].range <= cur_sy.voices[s2.v].range ? 1 : -1) > 0) v = s.v else v = s2.v @@ -2043,20 +2075,20 @@ else s = s.ts_next } if (!s2.grace) { // if end on a normal note - while (1) { + while (s) { if (s.v == v) path.push(s) if (s == s2) break s = s.ts_next } } else if (s.grace) { // if start/end in the same sequence while (1) { - if (s.v == v) +// if (s.v == v) path.push(s) if (s == s2) break s = s.next } @@ -2140,11 +2172,11 @@ /* -- draw a tuplet -- */ /* (the staves are not yet defined) */ /* (delayed output) */ /* See http://moinejf.free.fr/abcm2ps-doc/tuplets.xhtml * for the value of 'tp.f' */ -function draw_tuplet(s1, tp) { +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, tp = s1.tp.shift() // tuplet parameters if (!s1.tp.length) @@ -2152,18 +2184,22 @@ // treat the slurs and the nested tuplets upstaff = s1.st set_dscale(s1.st) for (s2 = s1; s2; s2 = s2.next) { - if (s2.type != C.NOTE && s2.type != C.REST) { - if (s2.type == C.GRACE) { - for (g = s2.extra; g; g = g.next) { - if (g.sls || g.sl1) - draw_slurs(g) - } - } - continue + switch (s2.type) { + case C.GRACE: + for (g = s2.extra; g; g = g.next) { + if (g.sls || g.sl1) + draw_slurs(g) + } + // fall thru + default: + continue + case C.NOTE: + case C.REST: + break } if (s2.sls || s2.sl1) draw_slurs(s2) if (s2.st < upstaff) upstaff = s2.st @@ -2170,20 +2206,22 @@ if (s2.tp) draw_tuplet(s2) if (s2.tpe) break } + + if (s2) + s2.tpe-- + + if (tp.f[0] == 1) // if 'when' == never + return // accept tuplets on many lines + if (!s2) { error(1, s1, "No end of tuplet in this music line") return } - s2.tpe-- - - if (tp.f[0] == 1) /* if 'when' == never */ - return - dir = tp.f[3] // 'where' if (!dir) { // if auto s3 = s1 while (s3.type != C.NOTE) s3 = s3.next @@ -2248,35 +2286,36 @@ /* if number only, draw it */ if (nb_only) { if (tp.f[2] == 1) /* if 'which' == none */ return + set_font("tuplet") xm = (s2.x + s1.x) / 2 - a = s2.x - s1.x // width around the middle - if (dir == C.SL_ABOVE) - ym = y_get(upstaff, 1, xm - a / 2, a) + 2 + if (dir == C.SL_ABOVE) // 8 = width around the middle + ym = y_get(upstaff, 1, xm - 4, 8) else - ym = y_get(upstaff, 0, xm - a / 2, a) - 10 + ym = y_get(upstaff, 0, xm - 4, 8) - + gene.curfont.size if (s1.stem * s2.stem > 0) { if (s1.stem > 0) xm += 1.5 else xm -= 1.5 } - if (tp.f[2] == 0) /* if 'which' == number */ - out_bnum(xm, ym, tp.p) + if (tp.f[2] == 0) // if 'which' == number + xy_str(xm, ym, tp.p.toString(), 'c') else - out_bnum(xm, ym, tp.p + ':' + tp.q) + xy_str(xm, ym, tp.p+':'+tp.q, 'c') for (s3 = s1; ; s3 = s3.next) { if (s3.x >= xm) break } if (dir == C.SL_ABOVE) { - ym += 10 + ym += gene.curfont.size if (s3.ymx < ym) s3.ymx = ym; y_set(upstaff, true, xm - 3, 6, ym) } else { if (s3.ymn > ym) @@ -2288,21 +2327,48 @@ // here, what is square bracket /*fixme: two staves not treated*/ /*fixme: to optimize*/ + + // first, get the x offsets + x1 = s1.x - 4 + + // end the bracket according to the last note duration + if (s2.dur > s2.prev.dur) { + s3 = s2.next + if (!s3 // maybe a note in an overlay voice + || s3.time != s2.time + s2.dur) { + for (s3 = s2.ts_next; s3; s3 = s3.ts_next) { + if (s3.seqst + && s3.time >= s2.time + s2.dur) + break + } + } +//fixme: s3 cannot be null (bar at end of staff) + x2 = s3 ? s3.x - s3.wl - 5 : realwidth - 6 + } else { + x2 = s2.x + 4 + r = s2.stem >= 0 ? 0 : s2.nhd + if (s2.notes[r].shhd > 0) + x2 += s2.notes[r].shhd + if (s2.st == upstaff + && s2.stem > 0) + x2 += 3.5 + } + + // above if (dir == C.SL_ABOVE) { /* sole or upper voice: the bracket is above the staff */ if (s1.st == s2.st) { - y1 = y2 = staff_tb[upstaff].topbar + 4 + y1 = y2 = staff_tb[upstaff].topbar + 2 } else { y1 = s1.ymx; y2 = s2.ymx } - x1 = s1.x - 4; if (s1.st == upstaff) { for (s3 = s1; !s3.dur; s3 = s3.next) ; ym = y_get(upstaff, 1, s3.x - 4, 8) if (ym > y1) @@ -2317,26 +2383,10 @@ ym = y_get(upstaff, 1, s3.x - 4, 8) if (ym > y2) y2 = ym } - // end the bracket according to the last note duration - if (s2.dur > s2.prev.dur) { - if (s2.next) - x2 = s2.next.x - s2.next.wl - 5 - else - x2 = realwidth - 6 - } else { - x2 = s2.x + 4; - r = s2.stem >= 0 ? 0 : s2.nhd - if (s2.notes[r].shhd > 0) - x2 += s2.notes[r].shhd - if (s2.st == upstaff - && s2.stem > 0) - x2 += 3.5 - } - xm = .5 * (x1 + x2); ym = .5 * (y1 + y2); a = (y2 - y1) / (x2 - x1); s0 = 3 * (s2.notes[s2.nhd].pit - s1.notes[s1.nhd].pit) / (x2 - x1) @@ -2374,11 +2424,11 @@ ym += dy; y1 = ym + a * (x1 - xm); y2 = ym + a * (x2 - xm); /* shift the slurs / decorations */ - ym += 8 + ym += 6 for (s3 = s1; ; s3 = s3.next) { if (s3.st == upstaff) { yy = ym + (s3.x - xm) * a if (s3.ymx < yy) s3.ymx = yy @@ -2388,27 +2438,15 @@ } else if (s3 == s2) { break } } + // below } else { /* lower voice of the staff: the bracket is below the staff */ /*fixme: think to all of that again..*/ - x1 = s1.x - 7 - if (s2.dur > s2.prev.dur) { - if (s2.next) - x2 = s2.next.x - s2.next.wl - 8 - else - x2 = realwidth - 6 - } else { - x2 = s2.x + 2 - if (s2.notes[s2.nhd].shhd > 0) - x2 += s2.notes[s2.nhd].shhd - } - if (s1.stem >= 0) { - x1 += 2; - x2 += 2 - } + if (s1.stem < 0) + x1 -= 2 if (s1.st == upstaff) { for (s3 = s1; !s3.dur; s3 = s3.next) ; y1 = y_get(upstaff, 0, s3.x - 4, 8) @@ -2457,11 +2495,11 @@ dy = yx - yy if (s3 == s2) break } - ym += dy - 10; + ym += dy - 8 y1 = ym + a * (x1 - xm); y2 = ym + a * (x2 - xm); /* shift the slurs / decorations */ ym -= 2 @@ -2514,11 +2552,11 @@ } else { if (m > 0 && p == s1.notes[m - 1].pit + 1) if (s1.notes[m - 1].shhd > sh) sh = s1.notes[m - 1].shhd } - x1 = s1.x + sh * .6 + x1 = s1.x + sh // * .6 if (job != 2) { for (m = 0; m < s2.nhd; m++) if (s2.notes[m] == not2) break @@ -2530,11 +2568,11 @@ } else { if (m > 0 && p == s2.notes[m - 1].pit + 1) if (s2.notes[m - 1].shhd < sh) sh = s2.notes[m - 1].shhd } - x2 += sh * .6 + x2 += sh // * .6 } switch (job) { case 0: p = (not1.pit & 1) ? not1.pit : not2.pit @@ -2578,13 +2616,13 @@ if (s1.dots && !(not1.pit & 1) && ((dir > 0 && !s1.dot_low) || (dir < 0 && s1.dot_low))) x1 += 5 - y = staff_tb[st].y + 3 * (p - 18) + y = staff_tb[st].y + 3 * (p - 18) + /* 1.0 * */ dir - h = (.03 * (x2 - x1) + 16) * dir + h = (.03 * (x2 - x1) + 16) * dir * cfmt.tieheight // anno_start(k1, 'slur') slur_out(x1, y, x2, y, dir, h, not1.tie_ty & C.SL_DOTTED) // anno_stop(k1, 'slur') } @@ -2794,14 +2832,57 @@ * - lyrics * - measure numbers * 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, + var p_voice, p_st, s, v, st, y, g, w, i, st, dx, top, bot, ymn, output_sav = output; + // set the staff offsets of a beam + function set_yab(s1, s2) { + var y, + k = realwidth / YSTEP, + i = (s1.x / k) | 0, + j = (s2.x / k) | 0, + a = (s1.ys - s2.ys) / (s1.xs - s2.xs), + b = s1.ys - s1.xs * a, + p_st = staff_tb[s1.st] + + k *= a + if (s1.stem > 0) { + while (i <= j) { + y = k * i + b + if (p_st.top[i] < y) + p_st.top[i] = y + i++ + } + } else { + while (i <= j) { + y = k * i + b + if (p_st.bot[i] > y) + p_st.bot[i] = y + i++ + } + } + } // set_yab() + output = "" + + // 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) + } + for (i = 0; i < YSTEP; i++) { + p_st.top[i] = 0 + p_st.bot[i] = 24 + } +// p_st.top.fill(0.) +// p_st.bot.fill(24.) + } /* calculate the beams but don't draw them (the staves are not yet defined) */ for (v = 0; v < voice_tb.length; v++) { var bm = {}, first_note = true; @@ -2809,40 +2890,30 @@ p_voice = voice_tb[v] for (s = p_voice.sym; s; s = s.next) { switch (s.type) { case C.GRACE: for (g = s.extra; g; g = g.next) { - if (g.beam_st && !g.beam_end) + if (g.beam_st && !g.beam_end) { self.calculate_beam(bm, g) + if (bm.s2) + set_yab(g, bm.s2) + } } break case C.NOTE: if ((s.beam_st && !s.beam_end) || (first_note && !s.beam_st)) { first_note = false; self.calculate_beam(bm, s) + if (bm.s2) + set_yab(s, bm.s2) } break } } } - /* initialize the min/max vertical offsets */ - 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) - } - for (i = 0; i < YSTEP; i++) { - p_st.top[i] = 0; - p_st.bot[i] = 24 - } -// p_st.top.fill(0.); -// p_st.bot.fill(24.) - } - set_tie_room(); draw_deco_near() /* set the min/max vertical offsets */ for (s = tsfirst; s; s = s.ts_next) { @@ -2850,11 +2921,14 @@ continue switch (s.type) { case C.GRACE: for (g = s.extra; g; g = g.next) { y_set(s.st, true, g.x - 2, 4, g.ymx + 1); - y_set(s.st, false, g.x - 2, 4, g.ymn - 1) + y_set(s.st, false, g.x - 2, 4, +//fixme: hack for slur/accidental far under the staff +// g.ymn - 1) + g.ymn - 5) } continue case C.MREST: y_set(s.st, true, s.x + 16, 32, s.ymx + 2) continue @@ -2877,11 +2951,15 @@ } else { dx = -8; w = s.beam_end ? 11 : 16 } y_set(s.st, true, s.x + dx, w, s.ymx); - y_set(s.st, false, s.x - s.wl, s.wl + s.wr, s.ymn) + ymn = s.ymn + if (s.notes[0].acc // set room for the accidental + && ymn > 3 * (s.notes[0].pit - 18) - 9) + ymn = 3 * (s.notes[0].pit - 18) - 9 + y_set(s.st, false, s.x - s.wl, s.wl + s.wr, ymn) } else { y_set(s.st, true, s.x - s.wl, s.wl + s.wr, s.ymx); if (s.stemless) { dx = -5; w = 10 @@ -2930,10 +3008,12 @@ // continue // } // draw the slurs and tuplets for ( ; s; s = s.next) { + if (s.play) + continue if (s.tp) draw_tuplet(s) if (s.sls || s.sl1) draw_slurs(s) } @@ -2972,16 +3052,16 @@ set_dscale(-1); output = output_sav } /* -- draw the name/subname of the voices -- */ -function draw_vname(indent) { +function draw_vname(indent, stl) { var p_voice, n, st, v, a_p, p, y, name_type, h, h2, staff_d = [] - for (st = cur_sy.nstaff; st >= 0; st--) { - if (cur_sy.st_print[st]) + for (st = stl.length; st >= 0; st--) { + if (stl[st]) break } if (st < 0) return @@ -2989,11 +3069,11 @@ for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] if (!p_voice.sym || !cur_sy.voices[v]) continue st = cur_sy.voices[v].st - if (!cur_sy.st_print[st]) + if (!stl[st]) continue if (p_voice.new_name) { name_type = 2 break } @@ -3005,32 +3085,32 @@ for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] if (!p_voice.sym || !cur_sy.voices[v]) continue st = cur_sy.voices[v].st - if (!cur_sy.st_print[st]) + if (!stl[st]) continue if (p_voice.new_name) delete p_voice.new_name; p = name_type == 2 ? p_voice.nm : p_voice.snm if (!p) continue if (!staff_d[st]) staff_d[st] = p else - staff_d[st] += "\\n" + p + staff_d[st] += "\n" + p } - if (staff_d.length == 0) + if (!staff_d.length) return set_font("voice"); h = gene.curfont.size h2 = h / 2 indent = -indent * .5 /* center */ for (st = 0; st < staff_d.length; st++) { if (!staff_d[st]) continue - a_p = staff_d[st].split('\\n'); + a_p = staff_d[st].split("\n"); y = staff_tb[st].y + staff_tb[st].topbar * .5 * staff_tb[st].staffscale + h2 * (a_p.length - 2) @@ -3047,12 +3127,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, - y, staffsep, dy, maxsep, mbot, val, p_voice, p_staff +var s, i, st, prev_staff, v, + y, staffsep, dy, maxsep, mbot, val, p_voice, p_staff, + sy = cur_sy /* set the scale of the voices */ for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] if (p_voice.scale != 1) @@ -3067,24 +3148,27 @@ } y = 0 if (st > nstaff) { st--; /* one staff, empty */ p_staff = staff_tb[st] - } else { + } +// else { p_staff = staff_tb[st] for (i = 0; i < YSTEP; i++) { val = p_staff.top[i] if (y < val) y = val } - } +// } /* draw the parts and tempo indications if any */ y += draw_partempo(st, y) - if (!gene.st_print[st]) - return y; + if (!gene.st_print[st]) { + p_staff.y = -y + return y + } /* set the vertical offset of the 1st staff */ y *= p_staff.staffscale; staffsep = cfmt.staffsep * .5 + p_staff.topbar * p_staff.staffscale @@ -3094,15 +3178,15 @@ y = p_staff.ann_top; p_staff.y = -y; /* set the offset of the other staves */ prev_staff = st - var sy_staff_prev = cur_sy.staves[prev_staff] + var sy_staff_prev = sy.staves[prev_staff] for (st++; st <= nstaff; st++) { - p_staff = staff_tb[st] if (!gene.st_print[st]) continue + p_staff = staff_tb[st] staffsep = sy_staff_prev.sep || cfmt.sysstaffsep; maxsep = sy_staff_prev.maxsep || cfmt.maxsysstaffsep; dy = 0 if (p_staff.staffscale == staff_tb[prev_staff].staffscale) { @@ -3130,11 +3214,16 @@ dy = maxsep; y += dy; p_staff.y = -y; prev_staff = st; - sy_staff_prev = cur_sy.staves[prev_staff] + while (1) { + sy_staff_prev = sy.staves[prev_staff] + if (sy_staff_prev) + break + sy = sy.next + } } mbot = 0 for (i = 0; i < YSTEP; i++) { val = staff_tb[prev_staff].bot[i] if (mbot > val) @@ -3179,10 +3268,11 @@ /* -- draw the staff systems and the measure bars -- */ function draw_systems(indent) { var s, s2, st, x, x2, res, sy, staves_bar, bar_force, xstaff = [], + stl = [], // all staves in the line bar_bot = [], bar_height = [], ba = [], // bars [symbol, bottom, height] sb = [], thb = [] @@ -3212,11 +3302,11 @@ /* -- draw a staff -- */ function draw_staff(st, x1, x2) { var w, ws, i, dy, ty, y = 0, ln = "", - stafflines = cur_sy.staves[st].stafflines, + stafflines = staff_tb[st].stafflines, l = stafflines.length if (!/[\[|]/.test(stafflines)) return // no line w = x2 - x1; @@ -3308,12 +3398,11 @@ ; xygl(s2.x, yb, "mrep") } else { xygl(x, yb, "mrep2") if (s.v == cur_sy.top_voice) - xy_str(x, yb + p_staff.topbar - 9, - s.bar_mrep.toString(), "c") + nrep_out(x, yb + p_staff.topbar, s.bar_mrep) } } if (bar_type == '||:') bar_type = '[|:' @@ -3360,20 +3449,19 @@ function out_bars() { var i, b, bx, l = ba.length set_font("annotation"); - if (gene.curfont.box) { - gene.curfont.box = false - bx = true - } + bx = gene.curfont.box + if (bx) + gene.curfont.box = 0 for (i = 0; i < l; i++) { b = ba[i]; // symbol, bottom, height draw_bar(b[0], b[1], b[2]) } if (bx) - gene.curfont.box = true + gene.curfont.box = bx set_sscale(-1) l = sb.length if (l) { // single bars [x, y, h] output += '\n' } } // out_bars() - // draw_systems() - draw_vname(indent) + // ---- draw_systems() ---- /* draw the staff, skipping the staff breaks */ - for (st = 0; st <= nstaff; st++) + for (st = 0; st <= nstaff; st++) { xstaff[st] = !cur_sy.st_print[st] ? -1 : 0; + stl[st] = cur_sy.st_print[st] // staff in the line + } bar_set(); draw_lstaff(0) for (s = tsfirst; s; s = s.ts_next) { if (bar_force && s.time != bar_force) { bar_force = 0 @@ -3434,13 +3523,15 @@ } sy = s.sy for (st = 0; st <= nstaff; st++) { x = xstaff[st] if (x < 0) { // no staff yet - if (sy.st_print[st]) + if (sy.st_print[st]) { xstaff[st] = staves_bar ? staves_bar : (s.x - s.wl - 2) + stl[st] = true + } continue } if (sy.st_print[st] // if not staff stop && sy.staves[st].stafflines == cur_sy.staves[st].stafflines) @@ -3483,24 +3574,24 @@ } if (nv == 0) draw_lstaff(s.x) } } - s2 = s.prev - if (!s2) - break - x2 = s2.x - if (s2.type != C.BAR) - x2 += s2.wr; st = s.st; x = xstaff[st] if (x >= 0) { + s2 = s.prev + if (!s2) + break + x2 = s2.type == C.BAR ? + s2.x : + s.x - s.xmx if (x >= x2) - continue + break draw_staff(st, x, x2) + xstaff[st] = s.x } - xstaff[st] = s.x break // default: //fixme:does not work for "%%staves K: M: $" */ //removed for K:/M: in empty staves // if (!cur_sy.st_print[st]) @@ -3519,10 +3610,13 @@ draw_staff(st, x, realwidth) } // and the bars out_bars() + + draw_vname(indent, stl) + // set_sscale(-1) } /* -- draw remaining symbols when the staves are defined -- */ // (possible hook) @@ -3533,11 +3627,11 @@ // bm.s2 = undefined for (s = p_voice.sym; s; s = s.next) { if (s.invis) { switch (s.type) { case C.KEY: - p_voice.key = s + p_voice.ckey = s default: continue case C.NOTE: // (beams may start on invisible notes) break } @@ -3554,28 +3648,26 @@ draw_beams(bm) } if (!s.invis) { anno_start(s); draw_note(s, !bm.s2); - anno_stop(s) + anno_a.push(s) } if (s == bm.s2) bm.s2 = null break case C.REST: - if (s.invis - || !staff_tb[st].topbar) + if (!staff_tb[st].topbar) break draw_rest(s); break case C.BAR: break /* drawn in draw_systems */ case C.CLEF: if (s.time >= staff_tb[st].clef.time) staff_tb[st].clef = s if (s.second - || s.invis || !staff_tb[st].topbar) break set_color(); set_sscale(st); anno_start(s); @@ -3597,45 +3689,43 @@ if (s.clef_small) y += 1 } xygl(x - 2, y, "oct") } - anno_stop(s) + anno_a.push(s) break case C.METER: p_voice.meter = s if (s.second -// || s.invis || !staff_tb[s.st].topbar) break set_color(); set_sscale(s.st); anno_start(s); - draw_meter(x, s); - anno_stop(s) + draw_meter(s); + anno_a.push(s) break case C.KEY: - p_voice.key = s + p_voice.ckey = s if (s.second - || s.invis || !staff_tb[s.st].topbar) break set_color(); set_sscale(s.st); anno_start(s); self.draw_keysig(x, s); - anno_stop(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_stop(s) + anno_a.push(s) break case C.GRACE: set_scale(s); draw_gracenotes(s) break @@ -3656,28 +3746,77 @@ error(2, s, "draw_symbols - Cannot draw symbol " + s.type) break } } set_scale(p_voice.sym); - draw_all_ties(p_voice); -// no need to reset the scale as in abcm2ps - set_color() } /* -- draw all symbols -- */ function draw_all_sym() { - var p_voice, v, - n = voice_tb.length + 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) + && p_voice.sym.x != undefined) { self.draw_symbols(p_voice) + draw_all_ties(p_voice); +// no need to reset the scale as in abcm2ps + set_color() + } } - draw_all_deco(); + 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) { Index: core/format.js ================================================================== --- core/format.js +++ core/format.js @@ -1,8 +1,8 @@ // abc2svg - format.js - formatting functions // -// 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 @@ -28,11 +28,11 @@ monospace: 1.35 }, fmt_lock = {} var cfmt = { - annotationfont: {name: "sans-serif", size: 12 }, + annotationfont: {name: "sans-serif", size: 12}, aligncomposer: 1, beamslope: .4, // max slope of a beam // botmargin: .7 * IN, // != 1.8 * CM, breaklimit: .7, breakoneoln: true, @@ -66,12 +66,14 @@ lineskipfac: 1.1, linewarn: true, maxshrink: .65, // nice scores maxstaffsep: 2000, maxsysstaffsep: 2000, + measrepnb: 1, measurefont: {name: "serif", style: "italic", size: 10}, measurenb: -1, + musicfont: {name: "music", src: musicfont, size: 24}, musicspace: 6, // notespacingfactor: 1.33 partsfont: {name: "serif", size: 15}, parskipfac: .4, partsspace: 8, @@ -94,18 +96,20 @@ sysstaffsep: 34, tempofont: {name: "serif", weight: "bold", size: 12}, textfont: {name: "serif", size: 16}, // textoption: undefined, textspace: 14, + tieheight: 1.0, titlefont: {name: "serif", 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}, vocalspace: 10, voicefont: {name: "serif", weight: "bold", size: 13}, // voicescale: 1, writefields: "CMOPQsTWw", @@ -155,27 +159,38 @@ } // create a new font font = cfmt[xxxfont]; if (!font) { // set-font- or new element - font = {} + font = { + pad: 0 + } } else { font = { name: font.name, size: font.size, - box: font.box + box: font.box, + pad: font.pad } } cfmt[xxxfont] = font; // fill the values a = p.match(/\s+(no)?box(\s|$)/) if (a) { // if box - if (a[1]) + if (a[1]) { font.box = false // nobox - else - font.box = true; + font.pad = 0 + } else { + font.box = true + font.pad = font.size * .4 - 3 + } + 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 p = p.replace(a[0], a[2]) } a = p.match(/\s+class=(.*?)(\s|$)/) if (a) { @@ -203,14 +218,12 @@ } // the font size is the last item a = p.match(/\s+([0-9.]+|\*)$/) if (a) { - if (a[1] != "*") { + if (a[1] != "*") font.size = Number(a[1]) - font.swfac = 0 - } p = p.replace(a[0], "") } a = p.match(/[- ]?[nN]ormal/) if (a) { @@ -231,42 +244,52 @@ a = p.match(/[- ]?[oO]blique/) if (a) { font.style = "oblique" p = p.replace(a[0], '') } + 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 } - font.swfac = 0 + + // 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 = parseFloat(param) + var v = param.toLowerCase().match(/([\d.]+)(.*)/) + if (!v) + return NaN - switch (param.slice(-2)) { - case "CM": + v[1] = Number(v[1]) + switch (v[2]) { case "cm": - v *= CM - break - case "IN": + return v[1] * CM case "in": - v *= IN - break - case "PT": // paper point in 1/72 inch - case "pt": - v *= .75 - break -// default: // ('px') // screen pixel in 1/96 inch - } - return v + return v[1] * IN + case "pt": // paper point in 1/72 inch + return v[1] * .75 + case "px": // screen pixel in 1/96 inch + case "": + return v[1] + } + return NaN } // set the infoname function set_infoname(param) { //fixme: check syntax: ' ["string"]' @@ -341,22 +364,15 @@ } } // set a voice specific parameter function set_v_param(k, v) { - if (curvoice) { - self.set_vp([k + '=', v]) - return - } - k = [k + '=', v]; - var vid = '*' - if (!info.V) - info.V = {} - if (info.V[vid]) - Array.prototype.push.apply(info.V[vid], k) + k = [k + '=', v] + if (curvoice) + set_kv_parm(k) else - info.V[vid] = k + memo_kv_parm('*', k) } function set_page() { if (!img.chg) return @@ -382,11 +398,14 @@ Abc.prototype.set_format = function(cmd, param) { var f, f2, v, i //fixme: should check the type and limits of the parameter values if (/.+font(-[\d])?$/.test(cmd)) { - param_set_font(cmd, param) + if (cmd == "soundfont") + cfmt.soundfont = param + else + param_set_font(cmd, param) return } switch (cmd) { case "aligncomposer": @@ -393,26 +412,19 @@ case "barsperstaff": case "infoline": case "measurenb": case "rbmax": case "rbmin": + case "measrepnb": case "shiftunison": v = parseInt(param) if (isNaN(v)) { syntax(1, "Bad integer value"); break } cfmt[cmd] = v break - case "microscale": - f = parseInt(param) - if (isNaN(f) || f < 4 || f > 256 || f % 1) { - syntax(1, errs.bad_val, "%%" + cmd) - break - } - set_v_param("uscale", f) - break case "bgcolor": case "fgcolor": case "dblrepbar": case "titleformat": cfmt[cmd] = param @@ -425,10 +437,11 @@ case "parskipfac": case "scale": case "slurheight": case "stemheight": case "stretchlast": + case "tieheight": f = parseFloat(param) if (isNaN(f)) { syntax(1, errs.bad_val, '%%' + cmd) break } @@ -444,16 +457,17 @@ break case "annotationbox": case "gchordbox": case "measurebox": case "partsbox": - cfmt[cmd.replace("box", "font")] // font - .box = get_bool(param) + param_set_font(cmd.replace("box", "font"), // font + "* * " + (get_bool(param) ? "box" : "nobox")) break case "bstemdown": case "breakoneoln": case "cancelkey": + case "checkbars": case "contbarnb": case "custos": case "decoerr": case "dynalign": case "flatbeams": @@ -472,15 +486,16 @@ case "timewarn": case "titlecaps": case "titleleft": cfmt[cmd] = get_bool(param) break - case "chordnames": - v = param.split(',') - cfmt.chordnames = {} - for (i = 0; i < v.length; i++) - cfmt.chordnames['CDEFGAB'[i]] = v[i] + case "chordalias": + v = param.split(/\s+/) + if (!v.length) + syntax(1, errs.bad_val, "%%chordalias") + else + abc2svg.ch_alias[v[0]] = v[1] || "" break case "composerspace": case "indent": case "infospace": case "maxstaffsep": @@ -499,10 +514,13 @@ if (isNaN(f)) syntax(1, errs.bad_val, '%%' + cmd) else cfmt[cmd] = f break + case "page-format": + user.page_format = get_bool(param) + break case "print-leftmargin": // to remove syntax(0, "$1 is deprecated - use %%printmargin instead", '%%' + cmd) cmd = "printmargin" // fall thru case "printmargin": @@ -650,38 +668,48 @@ if (font.weight) r += font.weight + " " if (font.style) r += font.style + " " - if (n.indexOf(' ') > 0) - n = '"' + n.replace(/"/g, "") + '"' // ' + if (n.indexOf('"') < 0 && n.indexOf(' ') > 0) + n = '"' + n + '"' return r + font.size.toFixed(1) + 'px ' + n } function style_font(font) { return 'font:' + st_font(font) } Abc.prototype.style_font = style_font // build a font class function font_class(font) { + var f = 'f' + font.fid + cfmt.fullsvg if (font.class) - return 'f' + font.fid + cfmt.fullsvg + ' ' + font.class - return 'f' + font.fid + cfmt.fullsvg + f += ' ' + font.class + if (font.box) + f += ' ' + 'box' + return f } // use the font function use_font(font) { if (!font.used) { font.used = true; - if (font.fid == undefined) { - font.fid = font_tb.length; + if (font.fid == undefined) { // if default font + font.fid = font_tb.length font_tb.push(font) if (!font.swfac) set_font_fac(font) + if (!font.pad) + font.pad = 0 } - font_style += "\n.f" + font.fid + cfmt.fullsvg + - " {" + style_font(font) + "}" + add_fstyle(".f" + font.fid + + (typeof document == "undefined" ? cfmt.fullsvg : "") + + "{" + style_font(font) + "}") + if (font.src) + add_fstyle("@font-face{\n\ + font-family:" + font.name + ";\n\ + src:" + font.src + "}") } } // get the font of the 'xxxfont' parameter function get_font(fn) { @@ -707,20 +735,24 @@ if (font.weight) font2.weight = font.weight if (font.style) font2.style = font.style } - if (font.class) - font2.class = font.class +// if (font.class) +// font2.class = font.class if (font.size) font2.size = font.size st = st_font(font2) + if (font.class) { + font2.class = font.class + st += ' '+ font.class + } fid = font_st[st] if (fid != undefined) return font_tb[fid] - font_st[st] = font_tb.length + font_st[st] = font_tb.length // will be the font id font2.fid = font2.used = undefined font = font2 } use_font(font) return font } Index: core/front.js ================================================================== --- core/front.js +++ core/front.js @@ -1,8 +1,8 @@ // abc2svg - front.js - ABC parsing front-end // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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 @@ -61,11 +61,11 @@ "4": "𝄪", "5": "𝄫" } // convert the escape sequences to utf-8 -function cnv_escape(src) { +function cnv_escape(src, flag) { var c, c2, dst = "", i, j = 0 while (1) { @@ -93,16 +93,25 @@ if (isNaN(j) || j < 0x20) { dst += src[++i] + "\u0306" // breve accent j = i + 1 continue } - dst += String.fromCharCode(j); + c = String.fromCharCode(j) + if (c == '\\') { + i += 4 + break + } + dst += c j = i + 5 continue case 't': // TAB dst += ' '; j = i + 1 + continue + case 'n': // new line (voice name) + dst += '\n'; + j = i + 1 continue default: c2 = abc_utf[src.slice(i, i + 2)] if (c2) { dst += c2; @@ -172,11 +181,13 @@ j = i + 2 continue } break } - dst += '\\' + c; + if (flag == 'w') + dst += '\\' + dst += c j = i + 1 } return dst + src.slice(j) } @@ -239,29 +250,35 @@ eol = eof return false } // tune_selected() // remove the comment at end of text - function uncomment(src, do_escape) { + // if flag, handle the escape sequences + // if flag is 'w' (lyrics), keep the '\'s + function uncomment(src, flag) { if (!src) return src - if (src.indexOf('%') >= 0) + var i = src.indexOf('%') + if (i == 0) + return '' + if (i > 0) src = src.replace(/([^\\])%.*/, '$1') .replace(/\\%/g, '%'); src = src.replace(/\s+$/, '') - if (do_escape && src.indexOf('\\') >= 0) - return cnv_escape(src) + if (flag && src.indexOf('\\') >= 0) + return cnv_escape(src, flag) return src } // uncomment() function end_tune() { generate() + set_page() // the page layout may have changed if (info.W) put_words(info.W); put_history(); - blk_flush(); - parse.state = 0; // file header + parse.state = 0 // file header + blk_flush() // (force end of block) cfmt = sav.cfmt; info = sav.info; char_tb = sav.char_tb; glovar = sav.glovar; maps = sav.maps; @@ -572,12 +589,13 @@ // music line (or free text) if (line1 != ':' || !/[A-Za-z+]/.test(line0)) { last_info = undefined; if (parse.state < 2) continue - parse.line.buffer = uncomment(file.slice(bol, eol), true); - parse_music_line() + parse.line.buffer = uncomment(file.slice(bol, eol)) + if (parse.line.buffer) + parse_music_line() continue } // information fields bol += 2 @@ -588,19 +606,19 @@ bol++ continue } break } - text = uncomment(file.slice(bol, eol), true) if (line0 == '+') { if (!last_info) { syntax(1, "+: without previous info field") continue } txt_add = ' '; // concatenate line0 = last_info } + text = uncomment(file.slice(bol, eol), line0) switch (line0) { case 'X': // start of tune if (parse.state != 0) { syntax(1, errs.ignored, line0) @@ -622,10 +640,13 @@ sav.maps = clone(maps, 1); sav.mac = clone(mac); sav.maci = clone(maci); info.X = text; parse.state = 1 // tune header + if (user.page_format + && blkdiv < 1) // (if no newpage) + blkdiv = 1 // the tune starts by the next SVG if (parse.tune_opts) tune_filter() continue case 'T': switch (parse.state) { Index: core/gchord.js ================================================================== --- core/gchord.js +++ core/gchord.js @@ -1,8 +1,8 @@ // abc2svg - gchord.js - chord symbols // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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 @@ -19,12 +19,12 @@ // -- parse a chord symbol / annotation -- // the result is added in the global variable a_gch // 'type' may be a single '"' or a string '"xxx"' created by U: function parse_gchord(type) { - var c, text, gch, x_abs, y_abs, type, - i, istart, iend, + var c, text, gch, x_abs, y_abs, type, + i, j, istart, iend, ann_font = get_font("annotation"), h_ann = ann_font.size, line = parse.line function get_float() { @@ -41,33 +41,28 @@ istart = parse.bol + line.index if (type.length > 1) { // U: text = type.slice(1, -1); iend = istart + 1 } else { - text = "" + i = ++line.index // search the ending double quote while (1) { - c = line.next_char() - if (!c) { + j = line.buffer.indexOf('"', i) + if (j < 0) { syntax(1, "No end of guitar chord") return } - if (c == '"') + if (line.buffer[j - 1] != '\\') break - if (c == '\\') { - text += c; - c = line.next_char() - } - text += c + i = j + 1 } + text = cnv_escape(line.buffer.slice(line.index, j)) + line.index = j iend = parse.bol + line.index + 1 } - if (curvoice.pos.gch == C.SL_HIDDEN) - return - - if (ann_font.box) - h_ann += 3; + if (ann_font.pad) + h_ann += ann_font.pad i = 0; type = 'g' while (1) { c = text[i] if (!c) @@ -153,10 +148,11 @@ break } i++ break } + gch.otext = gch.text // save for play accompaniment if (!a_gch) a_gch = [] a_gch.push(gch) } } @@ -169,20 +165,20 @@ var i, o, n, a, ip, b40, csa = p.split('/') for (i = 0; i < csa.length; i++) { // main and optional bass p = csa[i]; - o = p.search(/[ABCDEFG]/); + o = p.search(/[A-G]/) if (o < 0) continue // strange chord symbol! ip = o + 1 a = 0 - while (p[ip] == '#') { + while (p[ip] == '#' || p[ip] == '\u266f') { a++; ip++ } - while (p[ip] == 'b') { + while (p[ip] == 'b' || p[ip] == '\u266d') { a--; ip++ } n = note_names.indexOf(p[o]) + 16 b40 = (abc2svg.pab40(n, a) + transp + 200) % 40 @@ -204,43 +200,39 @@ if (gch.type == 'g') gch.text = gch_tr1(gch.text, curvoice.vtransp) } } -// -- build the chord indications / annotations -- +// 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; + a_gch = null + + if (curvoice.vtransp) + gch_transp(s) +} // csan_add + +// generator: build the chord symbols / annotations // (possible hook) Abc.prototype.gch_build = function(s) { - /* split the chord indications / annotations + /* split the chord symbols / annotations * and initialize their vertical offsets */ var gch, wh, xspc, ix, - pos = curvoice.pos.gch == C.SL_BELOW ? -1 : 1, y_above = 0, y_below = 0, y_left = 0, y_right = 0, GCHPRE = .4; // portion of chord before note - s.a_gch = a_gch; - a_gch = null - - if (curvoice.vtransp) - gch_transp(s) - // change the accidentals in the chord symbols, // 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') { - if (cfmt.chordnames) { - gch.otext = gch.text; // save for %%diagram - gch.text = gch.text.replace(/A|B|C|D|E|F|G/g, - function(c){return cfmt.chordnames[c]}) - if (cfmt.chordnames.B == 'H') - gch.text = gch.text.replace(/Hb/g, 'Bb') - } gch.text = gch.text.replace(/##|#|=|bb|b| /g, function(x) { switch (x) { case '##': return "𝄪" case '#': return "\u266f" @@ -251,39 +243,36 @@ return "𝄫" }); } else { if (gch.type == '@' && !user.anno_start && !user.anno_stop) { - gch.wh = [0, 0] + set_font(gch.font) + gch.text = str2svg(gch.text) + gch.text.wh = [0, 0] continue /* no width */ } } /* set the offsets and widths */ set_font(gch.font); - wh = strwh(gch.text); - gch.wh = wh - if (gch.font.box) - wh[1] += 4 + gch.text = str2svg(gch.text) + wh = gch.text.wh + 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; - y_above -= wh[1]; - gch.y = y_above break case '_': /* below */ xspc = wh[0] * GCHPRE if (xspc > 8) xspc = 8; gch.x = -xspc; - y_below -= wh[1]; - gch.y = y_below break case '<': /* left */ gch.x = -(wh[0] + 6); y_left -= wh[1]; gch.y = y_left + wh[1] / 2 @@ -296,17 +285,10 @@ default: // chord symbol xspc = wh[0] * GCHPRE if (xspc > 8) xspc = 8; gch.x = -xspc; - if (pos < 0) { /* below */ - y_below -= wh[1]; - gch.y = y_below - } else { - y_above -= wh[1]; - gch.y = y_above - } break } } /* move upwards the top and middle texts */ @@ -313,109 +295,202 @@ y_left /= 2; y_right /= 2 for (ix = 0; ix < s.a_gch.length; ix++) { gch = s.a_gch[ix] switch (gch.type) { - case '^': /* above */ - gch.y -= y_above - break case '<': /* left */ gch.y -= y_left break case '>': /* right */ gch.y -= y_right break - case 'g': // chord symbol - if (pos > 0) - gch.y -= y_above - break } } } // -- draw the chord symbols and annotations // (the staves are not yet defined) // (unscaled delayed output) // (possible hook) -Abc.prototype.draw_gchord = function(s, gchy_min, gchy_max) { - var gch, text, ix, x, y, y2, hbox, h, y_above, y_below, - - // adjust the vertical offset according to the chord symbols - w = 0, - yav = s.dur ? - (((s.notes[s.nhd].pit + s.notes[0].pit) >> 1) - 18) * 3 : - 12 // fixed offset on measure bars - - for (ix = 0; ix < s.a_gch.length; ix++) { - gch = s.a_gch[ix] - if (gch.wh[0] > w) - w = gch.wh[0] - } - y_above = y_get(s.st, 1, s.x - 3, w); - y_below = y_get(s.st, 0, s.x - 3, w) - if (y_above < gchy_max) - y_above = gchy_max - if (y_below > gchy_min) - y_below = gchy_min; - - set_dscale(s.st); - for (ix = 0; ix < s.a_gch.length; ix++) { - gch = s.a_gch[ix]; - use_font(gch.font); - set_font(gch.font); - h = gch.font.size; - hbox = gch.font.box ? 2 : 0; - w = gch.wh[0]; - x = s.x + gch.x; - text = gch.text - switch (gch.type) { - case '_': /* below */ - y = gch.y + y_below; - y_set(s.st, 0, x, w, y - hbox) - break - case '^': /* above */ - y = gch.y + y_above + hbox; - y_set(s.st, 1, x, w, y + h + hbox) - break - case '<': /* left */ +Abc.prototype.draw_gchord = function(i, s, x, y) { + var y2, + an = s.a_gch[i], + h = an.text.wh[1], + pad = an.font.pad, + w = an.text.wh[0] + pad * 2 + + switch (an.type) { + case '_': // below + y -= h + pad + y_set(s.st, 0, x, w, y - pad) + break + case '^': // above + y = y_get(s.st, 1, x, w) + y += pad + y_set(s.st, 1, x, w, y + h + pad) + break + case '<': // left + case '>': // right + if (an.type == '<') { /*fixme: what symbol space?*/ if (s.notes[0].acc) - x -= s.notes[0].shac; - y = gch.y + yav - h / 2 - break - case '>': /* right */ + x -= s.notes[0].shac + x -= pad + } else { if (s.xmx) x += s.xmx if (s.dots) - x += 1.5 + 3.5 * s.dots; - y = gch.y + yav - h / 2 - break - default: // chord symbol - if (gch.y >= 0) { - y = gch.y + y_above + hbox; - y_set(s.st, true, x, w, y + h + hbox) - } else { - y = gch.y + y_below; - y_set(s.st, false, x, w, y - hbox) - } - break - case '@': /* absolute */ - y = gch.y + yav - if (y > 0) { - y2 = y + h - if (y2 > staff_tb[s.st].ann_top) - staff_tb[s.st].ann_top = y2 - } else { - if (y < staff_tb[s.st].ann_bot) - staff_tb[s.st].ann_bot = y - } - break - } - if (user.anno_start) - user.anno_start("annot", gch.istart, gch.iend, - x - 2, y + h + 2, w + 4, h + 4, s) - xy_str(x, y, text, null, null, gch.wh) - if (user.anno_stop) - user.anno_stop("annot", gch.istart, gch.iend, - x - 2, y + h + 2, w + 4, h + 4, s) - } -} + x += 1.5 + 3.5 * s.dots + x += pad + } + y += (s.type == C.NOTE ? + (((s.notes[s.nhd].pit + s.notes[0].pit) >> 1) - + 18) * 3 : + 12) // fixed offset on rests and bars + - h / 2 + break + default: // chord symbol + if (y >= 0) { + y += pad + y_set(s.st, 1, x, w, y + h + pad) + } else { + y -= h + pad + y_set(s.st, 0, x, w, y - pad) + } + break + case '@': // absolute + y += (s.type == C.NOTE ? + (((s.notes[s.nhd].pit + s.notes[0].pit) >> 1) - + 18) * 3 : + 12) // fixed offset on rests and bars + + h * .2 + if (y > 0) { + y2 = y + h * .8 + if (y2 > staff_tb[s.st].ann_top) + staff_tb[s.st].ann_top = y2 + } else { + y2 = y + if (y2 < staff_tb[s.st].ann_bot) + staff_tb[s.st].ann_bot = y2 + } + break + } + use_font(an.font) + set_font(an.font) + set_dscale(s.st) + if (user.anno_start) + user.anno_start("annot", an.istart, an.iend, + x - 2, y + h + 2, w + 4, h + 4, s) + xy_str(x, y, 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) { + an = s.a_gch + if (!an) + continue + i = an.length - j - 1 + an = an[i] + if (!an) + continue + if ((an.type == 'g' && pos > 0) + || an.type == '^') { + 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 == '_') { + continue + } else { + x = s.x + an.x + y = an.y + } + self.draw_gchord(i, s, x, y) + } + } // set_an_yu() + + function set_an_yl(i) { + var an, x, y, w + + for (var s = san1 ; s; s = s.ts_next) { + an = s.a_gch + if (!an) + continue + an = an[i] + if (!an + || !((an.type == 'g' && pos < 0) + || an.type == '_')) + continue + x = s.x + an.x + w = an.text.wh[0] + if (w && x + w > realwidth) // let the text inside the page + x = realwidth - w + y = y_get(s.st, 0, x, w) // y / staff + if (an.type == 'g' && y > minmax[s.st].ydn) + y = minmax[s.st].ydn + self.draw_gchord(i, s, x, y) + } + } // set_an_yl() + + // get the number of chord symbols / annotations + // and the vertical offset for the chord symbols + for (i = 0; i <= nstaff; i++) + minmax[i] = { + ydn: staff_tb[i].botbar - 3, + yup: staff_tb[i].topbar + 4 + } + for (s = tsfirst; s; s = s.ts_next) { + an = s.a_gch + if (!an) + continue + if (!san1) + san1 = s // first chord symbol / annotation + i = an.length + if (i > n_an) + n_an = i + while (--i >= 0) { + if (an[i].type == 'g') { + an = an[i] + w = an.text.wh[0] + if (pos > 0) { + y = y_get(s.st, true, s.x, w) + if (y > minmax[s.st].yup) + minmax[s.st].yup = y + } else if (pos < 0) { + y = y_get(s.st, false, s.x, w) + if (y < minmax[s.st].ydn) + minmax[s.st].ydn = y + } + break + } + } + } + if (!san1) + return // no chord symbol nor annotation + + // draw the elements + set_dscale(-1) // restore the scale parameters + for (i = 0; i < n_an; i++) { + set_an_yu(i) // upper offsets + set_an_yl(i) // lower offsets + } +} // draw_all_chsy() 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-2019 Jean-Francois Moine - LGPL3+ +// Copyright (C) 2014-2020 Jean-Francois Moine - LGPL3+ Index: core/lyrics.js ================================================================== --- core/lyrics.js +++ core/lyrics.js @@ -107,14 +107,13 @@ break case '!': deco_cnv([d.slice(1, -1)], s, s.prev) break case '"': - a_gch = s.a_gch; parse_gchord(d) - if (a_gch) - self.gch_build(s) + if (a_gch) // if no error + csan_add(s) break } s = s.next; i++ } @@ -121,11 +120,11 @@ curvoice.lyric_cont = s } /* -- parse a lyric (vocal) line (w:) -- */ function get_lyrics(text, cont) { - var s, word, p, i, j, ly + var s, word, p, i, j, ly, dfnt if (curvoice.ignore) return if (curvoice.pos.voc != C.SL_HIDDEN) curvoice.have_ly = true @@ -154,10 +153,11 @@ return } } /* scan the lyric line */ + dfnt = gene.curfont // font at start of line p = text; i = 0 while (1) { while (p[i] == ' ' || p[i] == '\t') i++ @@ -230,14 +230,13 @@ return } if (word && s.pos.voc != C.SL_HIDDEN) { if (word.match(/^\$\d/)) { - if (word[1] == '0') - set_font("vocal") - else - set_font("u" + word[1]); + gene.curfont = word[1] == '0' ? + dfnt : + get_font("u" + word[1]) word = word.slice(2) } ly = { t: word, font: gene.curfont, Index: core/mei.js ================================================================== --- core/mei.js +++ core/mei.js @@ -1,8 +1,8 @@ // abc2svg - mei.js - MEI front-end // -// Copyright (C) 2019 Jean-Francois Moine +// Copyright (C) 2019-2020 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 @@ -798,11 +798,11 @@ // new page for text pb: function(tag) { var s //fixme: tag.n (page number) - if (parse.state == 3) { + if (parse.state >= 2) { s = new_block("newpage") s.param = tag.n || "" } else { blk_flush() block.newpage = true @@ -914,11 +914,11 @@ return true // skip }, // revisionDesc() // newline for music sb: function(tag) { - curvoice.last_sym.eoln = true + voice_tb[0].eoln = true }, // sb() // score: function(tag) { // tag.n // tag.id @@ -1357,23 +1357,23 @@ s.pos.gch = C.SL_ABOVE; break case "below": s.pos.gch = C.SL_BELOW; break } break } - a_gch = s.a_gch || [] t = curr.text.split('\n') if (t[0][0] == ' ') t[0] = t[0].slice(1) + if (!s.a_gch) + s.a_gch = [] for (i = 0; i < t.length; i++) { gch = { type: ty, font: font, text: t[i] } - a_gch.push(gch) + s.a_gch.push(gch) } - self.gch_build(s) curr.text = undefined }, // dir() // end of division div: function(tag) { @@ -2276,6 +2276,8 @@ cfmt.graceslurs = false glovar.ulen = C.BLEN / 4 param_set_font("dynamfont", "serifItalicBold 16") parse_mei(xml2tree(mei)) // convert to internal music representation + + parse.state = 0 // end of tune } // mei2mus() Index: core/modules.js ================================================================== --- core/modules.js +++ core/modules.js @@ -1,8 +1,8 @@ // abc2svg - modules.js - module handling // -// Copyright (C) 2018-2019 Jean-Francois Moine +// Copyright (C) 2018-2020 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -24,27 +24,35 @@ onerror() } } abc2svg.modules = { - ambitus: { fn: 'ambitus-1.js' }, - beginps: { fn: 'psvg-1.js' }, - break: { fn: 'break-1.js' }, - capo: { fn: 'capo-1.js' }, - clip: { fn: 'clip-1.js' }, - clairnote: { fn: 'clair-1.js' }, - voicecombine: { fn: 'combine-1.js' }, - diagram: { fn: 'diag-1.js' }, - equalbars: { fn: 'equalbars-1.js' }, - grid: { fn: 'grid-1.js' }, - grid2: { fn: 'grid2-1.js' }, - MIDI: { fn: 'MIDI-1.js' }, - pageheight: { fn: 'page-1.js' }, - percmap: { fn: 'perc-1.js' }, - soloffs: { fn: 'soloffs-1.js' }, - sth: { fn: 'sth-1.js' }, - temperament: { fn: 'temper-1.js' }, + ambitus: {}, + begingrid: { fn: 'grid3' }, + beginps: { fn: 'psvg' }, + break: {}, + capo: {}, + chordnames: {}, + clip: {}, + clairnote: { fn: 'clair' }, + voicecombine: { fn: 'combine' }, + diagram: { fn: 'diag' }, + equalbars: {}, + gamelan: {}, + grid: {}, + grid2: {}, + jazzchord: {}, + jianpu: {}, + mdnn: {}, + MIDI: {}, + pageheight: { fn: 'page' }, + pedline: {}, + percmap: { fn: 'perc' }, + soloffs: {}, + sth: {}, + strtab: {}, + temperament: { fn: 'temper' }, nreq: 0, hooks: [], g_hooks: [], @@ -57,48 +65,54 @@ load: function(file, relay, errmsg) { function get_errmsg() { if (typeof user == 'object' && user.errmsg) return user.errmsg - if (typeof printErr == 'function') - return printErr + if (typeof abc2svg.printErr == 'function') + return abc2svg.printErr if (typeof alert == 'function') return function(m) { alert(m) } if (typeof console == 'object') return console.log return function(){} + } // get_errmsg() + + // call back functions for loadjs() + function load_end() { + if (--abc2svg.modules.nreq == 0) + abc2svg.modules.cbf() + } + function load_ko(fn) { + abc2svg.modules.errmsg('Error loading the module ' + fn) + load_end() } // test if some keyword in the file - var m, r, + var m, i, fn, nreq_i = this.nreq, ls = file.match(/(^|\n)(%%|I:).+?\b/g) if (!ls) return true this.cbf = relay || // (only one callback function) function(){} this.errmsg = errmsg || get_errmsg() - for (var i = 0; i < ls.length; i++) { - m = abc2svg.modules[ls[i].replace(/\n?(%%|I:)/, '')] + for (i = 0; i < ls.length; i++) { + fn = ls[i].replace(/\n?(%%|I:)/, '') + m = abc2svg.modules[fn] if (!m || m.loaded) continue m.loaded = true // load the module - this.nreq++; - abc2svg.loadjs(m.fn, - function() { // if success - if (--abc2svg.modules.nreq == 0) - abc2svg.modules.cbf() - }, - function() { // if error - abc2svg.modules.errmsg('error loading ' + m.fn); - if (--abc2svg.modules.nreq == 0) - abc2svg.modules.cbf() - }) + if (m.fn) + fn = m.fn + this.nreq++ + abc2svg.loadjs(fn + "-1.js", + load_end, + function() {load_ko(fn)}) } return this.nreq == nreq_i } } // modules Index: core/music.js ================================================================== --- core/music.js +++ core/music.js @@ -1,8 +1,8 @@ // abc2svg - music.js - music generation // -// 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 @@ -51,20 +51,20 @@ var dx_tb = new Float32Array([ 10, // FULL 10, // EMPTY 11, // OVAL 13, // OVALBARS - 13 // SQUARE + 15 // SQUARE ]) // head width - index = note head type var hw_tb = new Float32Array([ - 4.5, // FULL + 4.7, // FULL 5, // EMPTY 6, // OVAL - 7, // OVALBARS - 8 // SQUARE + 7.2, // OVALBARS + 7.5 // SQUARE ]) /* head width for voice overlap - index = note head type */ var w_note = new Float32Array([ 3.5, // FULL @@ -88,21 +88,23 @@ for (flags = 5; dur != 0; dur >>= 1, flags--) { if (dur & 1) break } dur >>= 1 - if ((dur + 1) & dur) - error(0, s, "Non standard note duration $1", dur_o) + if ((dur + 1) & dur) { + if (s.type != C.REST || dur_o != s.p_v.wmeasure) + error(0, s, "Non standard note duration $1", dur_o) + } while (dur >> dots > 0) dots++ flags -= dots if (flags >= 0) { head = C.FULL } else switch (flags) { default: - error(1, s, "Note too long"); + error(1, s, "Note too long") flags = -4 /* fall thru */ case -4: head = C.SQUARE break @@ -335,18 +337,37 @@ else s.p_v.sym = s; next.prev = s } function lktsym(s, next) { // time linkage + s.ts_next = next if (next) { - s.ts_next = next; s.ts_prev = next.ts_prev if (s.ts_prev) s.ts_prev.ts_next = s; next.ts_prev = s } else { - s.ts_next = s.ts_prev = null +//fixme +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 + if (!next) + return + next.seqst = next.time != s.time || + (w_tb[s.type] != w_tb[next.type] + && w_tb[s.type] != 0) + if (next.seqst) { + self.set_width(next) + next.shrink = next.wl + if (next.prev) + next.shrink += next.prev.wr + next.space = 0 } } /* -- unlink a symbol -- */ function unlksym(s) { @@ -364,10 +385,18 @@ } else { s.ts_next.seqst = true; s.ts_next.shrink = s.shrink; s.ts_next.space = s.space } + } else { + if (s.ts_next.seqst + && s.ts_prev && s.ts_prev.seqst + && !w_tb[s.ts_prev.type]) { + s.ts_next.seqst = false + s.shrink = s.ts_next.shrink + s.space = s.ts_next.space + } } s.ts_next.ts_prev = s.ts_prev } if (s.ts_prev) s.ts_prev.ts_next = s.ts_next @@ -410,12 +439,10 @@ /* link in time */ while (!s.seqst) s = s.ts_prev; lktsym(new_s, s) - if (new_s.ts_prev.type != C.CLEF) - new_s.seqst = true return new_s } /* -- set the staff of the floating voices -- */ /* this function is called only once per tune */ @@ -585,24 +612,28 @@ 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) { + 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.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 if (s0.shrink) s0.shrink += dw - if (s0 == s) + if (s0 == s + || s0.type == C.BAR) break } } } s0 = s; - w0 = ch.wh[0]; + w0 = ch.text.wh[0]; n = 0; // x = ch.font.box ? -2 : 0 x = 0 break } @@ -616,16 +647,16 @@ for (ix = 0; ix < s.a_gch.length; ix++) { gch = s.a_gch[ix] switch (gch.type) { case '<': /* left */ - w = gch.wh[0] + wlnote + w = gch.text.wh[0] + wlnote if (w > wlw) wlw = w break case '>': /* right */ - w = gch.wh[0] + s.wr + w = gch.text.wh[0] + s.wr if (w > arspc) arspc = w break } } @@ -677,13 +708,14 @@ xx = s.notes[m].shhd if (xx < 0) { if (wlnote < -xx + 5) wlnote = -xx + 5 } - if (s.notes[m].acc) { + acc = s.notes[m].acc + if (acc) { tmp = s.notes[m].shac + - (s.notes[m].micro ? 5.5 : 3.5) + (typeof acc != "number" ? 5.5 : 3.5) if (wlnote < tmp) wlnote = tmp } } if (s2) { @@ -835,20 +867,25 @@ /* have room for the repeat numbers / chord indication */ if (s.text && s.text.length < 4 && s.next && s.next.a_gch) { set_font("repeat"); s.wr += strwh(s.text)[0] + 2 + if (cfmt.measurenb > 0 & s.bar_num + && s.bar_num % cfmt.measurenb) + s.wr += 4 } return case C.CLEF: // (there may be invisible clefs in empty staves) if (s.invis) { s.wl = s.wr = 1 // (!! not 0 !!) return } - s.wl = s.clef_small ? 9 : 12 - s.wr = s.clef_small ? 7 : 12 + s.wl = s.clef_small ? 11 : 12 + s.wr = s.clef_small ? 10 : 12 + 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 @@ -1024,20 +1061,14 @@ } return space } // set the natural space -function set_space(s) { - var space, prev_time, len, - s2 = s.ts_prev - - if (s.play) - return 0 - while (s2.play) // if play symbol - s2 = s2.ts_prev - prev_time = s2.time - len = s.time - prev_time // time skip +function set_space(s, ptime) { + var space, len, s2, stemdir + + len = s.time - ptime // time skip if (!len) { switch (s.type) { case C.MREST: return s.wl @@ -1096,14 +1127,14 @@ /* decrease spacing when stem down followed by stem up */ /*fixme:to be done later, after x computed in sym_glue*/ if (s.type == C.NOTE && s.nflags >= -1 && s.stem > 0) { - var stemdir = true + stemdir = true for (s2 = s.ts_prev; - s2 && s2.time == prev_time; + s2 && s2.time == ptime; s2 = s2.ts_prev) { if (s2.type == C.NOTE && (s2.nflags < -1 || s2.stem > 0)) { stemdir = false break @@ -1124,104 +1155,98 @@ } } return space } -// set a fixed spacing inside tuplets +// set the spacing inside tuplets or L: factor function set_sp_tup(s, s_et) { - var s2, - tim = s.time, - endtime = s_et.time + s_et.dur, - ttim = endtime - tim, - space = time2space(s, ttim / s.tp[0].q) * s.tp[0].q / ttim - - // start on the second note/rest - do { - s = s.ts_next - } while (!s.seqst) -//fixme: problem when BAR inside the tuplet sequence (in case user error) -// while (!s.dur) -// s = s.ts_next -// while (!s.seqst) -// s = s.ts_prev - - // stop outside the tuplet sequence - // and add a measure bar when at end of tune - do { - if (!s_et.ts_next) { - s2 = add_end_bar(s_et); - s_et = s2 - } else { - s_et = s_et.ts_next - } - } while (!s_et.seqst) - - // check the minimum spacing - s2 = s + var tim = s.time, + ttim = s_et.time - tim, + sp = time2space(s, ttim), // whole time spacing + s2 = s, + wsp = 0 + + // compute the whole spacing while (1) { - if (s2.dur - && s2.dur * space < s2.shrink) - space = s2.shrink / s2.dur + s2 = s2.ts_next + if (s2.seqst) { + wsp += s2.space + if (s2.bar_type) + wsp += 10 // (fixme: not exact) + } if (s2 == s_et) break - s2 = s2.ts_next } + sp = (sp + wsp) / 2 / ttim // mean spacing per time unit - // set the space values while (1) { + s = s.ts_next if (s.seqst) { - s.space = (s.time - tim) * space; + s.space = sp * (s.time - tim) tim = s.time } if (s == s_et) break - s = s.ts_next } } -// create an invisible bar for end of music lines -function add_end_bar(s) { - var bar = { +// return an empty bar +function _bar(s) { + return { type: C.BAR, bar_type: "|", fname: s.fname, istart: s.istart, iend: s.iend, v: s.v, p_v: s.p_v, st: s.st, dur: 0, - seqst: true, - invis: true, - time: s.time + s.dur, nhd: 0, notes: [{ - pit: s.notes[0].pit + pit: s.notes ? s.notes[0].pit : 22 }], - wl: 0, - wr: 0, - prev: s, - ts_prev: s, - shrink: s.wr + 3 - } - s.next = s.ts_next = bar - return bar + prev: s + } +} // _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) + s.next.prev = b + if (s.ts_next) + s.ts_next.ts_prev = b + s.next = s.ts_next = b + b.space = set_space(b, s.time) + return b } /* -- set the width and space of all symbols -- */ // this function is called once for the whole tune function set_allsymwidth() { - var maxx, new_val, s_tupc, s_tupn, st, s_chs, + var maxx, val, st, s_chs, tim, stup, s = tsfirst, s2 = s, xa = 0, xl = [], - wr = [], - ntup = 0 + wr = [] /* loop on all symbols */ maxx = xa + tim = s.time while (1) { do { if (s.a_gch && !s_chs) s_chs = s; self.set_width(s); @@ -1228,25 +1253,40 @@ st = s.st if (xl[st] == undefined) xl[st] = 0 if (wr[st] == undefined) wr[st] = 0; - new_val = xl[st] + wr[st] + s.wl - if (new_val > maxx) - maxx = new_val; + val = xl[st] + wr[st] + s.wl + if (val > maxx) + maxx = val s = s.ts_next } while (s && !s.seqst); // set the spaces of the time sequence s2.shrink = maxx - xa - if (!ntup) // if not inside a tuplet sequence - s2.space = s2.ts_prev ? set_space(s2) : 0 + s2.space = s2.ts_prev ? set_space(s2, tim) : 0 + + if (s2.dur && s2.dur != s2.notes[0].dur) { + if (!stup) { + stup = s2 + while (stup.prev + && stup.prev.dur + && stup.prev.dur != stup.prev.notes[0].dur) + stup = stup.prev + while (!stup.seqst) + stup = stup.ts_prev + } + } else if (stup && stup.v == s2.v) { + set_sp_tup(stup, s2) + stup = null + } if (!s2.shrink && !s2.space && s2.type == C.CLEF) { delete s2.seqst; /* no space */ - s2.time = s2.ts_prev.time + s2.time = tim } + tim = s2.time if (!s) break // update the min left space per staff for (st = 0; st < wr.length; st++) @@ -1255,49 +1295,17 @@ do { st = s2.st; xl[st] = xa if (s2.wr > wr[st]) wr[st] = s2.wr - if (s2.tp) { // start of tuplet - if (!ntup - && !s_tupc) - s_tupc = s2 // keep the first tuplet address - ntup += s2.tp.length - } - if (s2.tpe) // end of tuplet - ntup -= s2.tpe; s2 = s2.ts_next } while (!s2.seqst) } // let the chord symbols at the same offset if (s_chs) set_w_chs(s_chs) - - // adjust the spacing inside the tuplets - s = s_tupc - if (!s) - return - do { - s2 = s; // start of tuplet - ntup = 0 - while (1) { // search the end of the tuplet sequence - if (s.tp) - ntup += s.tp.length - if (s.tpe) - ntup -= s.tpe - if (!ntup) - break - s = s.ts_next - } - - set_sp_tup(s2, s) - - do { // search next tuplet - s = s.ts_next - } while (s && !s.tp) - } while (s) } // insert a rest, this one replacing a sequence or a measure function to_rest(so) { var s = clone(so) @@ -1539,11 +1547,10 @@ new_s = sym_add(p_voice, C.CUSTOS); new_s.next = s; s.prev = new_s; lktsym(new_s, s); - new_s.seqst = true; 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; @@ -1560,132 +1567,215 @@ } new_s.stemless = true } /* -- define the beginning of a new music line -- */ -function set_nl(s) { - var s2, s3, p_voice, done +function set_nl(s) { // s = start of line + var p_voice, done, tim, ptyp - // set the end of line marker + // 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 + + function new_type(s) { + var t = s.bar_type.match(/(:*)([^:]*)(:*)/) + // [1] = starting ':'s, [2] = middle, [3] = ending ':'s + + if (!t[3]) { // if start of variant + // |1 -> | [1 + // :|]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] + t2 = '[|' + t[3] + } else { + // :][: -> :|] [|: + i = (t[2].length / 2) | 0 + t1 = t[1] + '|' + t[2].slice(0, i) + t2 = t[2].slice(i) +'|' + t[3] + } + } // new_typ() + + s = so // start of sequence + while (s.ts_prev + && s.ts_prev.time == so.time) { + s = s.ts_prev + if (s.bar_type) + s1 = s // first previous bar + } + if (!s1) + return so + + // search the insertion point in the new line + for (s = so; ; s = s.ts_next) { + switch (s.type) { + default: + if (w_tb[s.type]) + break + case C.KEY: + case C.METER: + case C.CLEF: + continue + } + break + } + 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 + s2.bar_type = t2 + lkvsym(s2, s1.next) // voice + while (1) { + if (s.type != C.BAR + || s.v > s2.v) + break + s = s.ts_next + } + lktsym(s2, s) // time + if (s == so) + so = s2 + if (s1.text) { + s2.invis = true + delete s1.text + delete s1.rbstart + } + delete s2.a_dd + } + s1 = s1.ts_next + } + return so + } // bardiv() + + // set the start of line marker function set_eol(s) { if (cfmt.custos && voice_tb.length == 1) custos_add(s) s.nl = true + s = s.ts_prev + if (s.type != C.BAR) + add_end_bar(s) } // set_eol() - // set the eol on the next symbol - function set_eol_next(s) { - if (!s.next) { // special case: the voice stops here - set_eol(s) - return s - } - for (s = s.ts_next; s; s = s.ts_next) { - if (s.seqst) { - set_eol(s) - break - } - } - return s - } // set_eol_next() - - // go to the end of the time sequence - // for adding keywarn or timewarn - while (s) { - if (!s.ts_next) - return // null - if (s.ts_next.seqst) - break - s = s.ts_next - } - - // if keywarn or timewarn, move K: and M: to the end of the previous line - if (cfmt.keywarn || cfmt.timewarn) { - for (s2 = s.ts_next; s2; s2 = s2.ts_next) { - switch (s2.type) { - case C.BAR: - case C.CLEF: - continue + // put the warning symbols + // the new symbols go in the previous line + function do_warn(s) { // start of next line + var s1, s2, s3 + + // 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 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 && !cfmt.timewarn) - continue - s3 = s2.ts_prev; - if (s3 == s) { // if next symbol - s = s2 // advance the eol - continue - } - unlksym(s2); // remove - lktsym(s2, s.ts_next); // link in time at eol - s = s2 - while (1) { // link in voice - s2 = s2.ts_prev - if (s2.v == s.v) { - s.next = s2.next; - s.prev = s2; - s.next.prev = s; - s2.next = s + if (s2.type == C.METER) { + if (!cfmt.timewarn) + continue + s1 = s.ts_prev + } + // fall thru + case C.CLEF: + if (!s2.prev) // start of voice + continue + if (s2.type == C.CLEF) { + for (s1 = s.ts_prev; s1; s1 = s1.ts_prev) { + switch (s1.type) { + case C.BAR: + case C.KEY: + case C.METER: + continue + } + break + } + } + + // 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) break - } } + lkvsym(s3, s1) // voice link // care with spacing - if (s.type != s.ts_prev.type) { - if (!s.seqst) { - s.seqst = true; - s.shrink = s.wl + s.prev.wr; - s.space = s.ts_next.space; - s.ts_next.space = 0 - } - } else { - delete s.seqst - } - - s2 = s3 // restart + if (s3.seqst) { + s3.shrink = s3.wl + 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 + } continue } if (w_tb[s2.type]) break // symbol with a width } - } + } // do_warn() + + // divide the left repeat and a 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.type) { - case C.CLEF: - case C.BAR: + 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: - return set_eol_next(s) + set_eol(s) + return s } /* go back to handle the staff breaks at end of line */ - for (; s; s = s.ts_prev) { + for (s = s.ts_prev; s; s = s.ts_prev) { if (s.seqst && s.type != C.CLEF) break } done = 0 + ptyp = s.type for ( ; ; s = s.ts_next) { if (!s) return s - if (!s.seqst) + if (s.type == ptyp) continue + ptyp = s.type if (done < 0) break switch (s.type) { case C.STAVES: - if (s.ts_prev && s.ts_prev.type == C.BAR) + 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 && s.ts_next.type != C.CLEF) break @@ -1728,27 +1818,27 @@ function get_ck_width() { var r0, r1, p_voice = voice_tb[0] self.set_width(p_voice.clef); - self.set_width(p_voice.key); + self.set_width(p_voice.ckey); self.set_width(p_voice.meter) return [p_voice.clef.wl + p_voice.clef.wr + - p_voice.key.wl + p_voice.key.wr, + p_voice.ckey.wl + p_voice.ckey.wr, p_voice.meter.wl + p_voice.meter.wr] } -// get the width of the symbols up to the next eoln or eof +// get the width of the symbols up to the next soln or eof // also, set a x (nice spacing) to all symbols // two returned values: width of nice spacing, width with max shrinking -function get_width(s, last) { +function get_width(s, next) { var shrink, space, w = 0, wmx = 0, sp_fac = (1 - cfmt.maxshrink) - do { + while (s != next) { if (s.seqst) { shrink = s.shrink wmx += shrink if ((space = s.space) < shrink) w += shrink @@ -1755,76 +1845,75 @@ else w += shrink * cfmt.maxshrink + space * sp_fac s.x = w } - if (s == last) - break s = s.ts_next - } while (s) - if (last) - wmx += last.wr // big key signatures may be wide enough + } + if (next) + wmx += next.wr // big key signatures may be wide enough return [w, wmx] } /* -- search where to cut the lines according to the staff width -- */ function set_lines( s, /* first symbol */ - last, /* last symbol / null */ + 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, - nlines, cut_here + nlines, + 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 && last && last.type == C.BAR - && last.next && last.next.time == last.next.time - && last.next.type == C.KEY) - last = last.next - - ws = get_width(s, last) // 2 widths: nice and shrinked + if (cfmt.keywarn && next + && next.type == C.KEY && !last.dur) { + ws[0] += next.wr + ws[1] += next.wr + } // check if the symbols can enter in one line if (ws[1] + indent < lwidth) { - if (last) - last = set_nl(last) - return last + if (next) + next = set_nl(next) + return next || last } /* loop on cutting the tune into music lines */ wwidth = ws[0] + indent while (1) { nlines = Math.ceil(wwidth / lwidth) if (nlines <= 1) { - if (last) - last = set_nl(last) - return last + if (next) + next = set_nl(next) + return next || last } s2 = first = s; xmin = s.x - s.shrink - indent; xmax = xmin + lwidth; xmid = xmin + wwidth / nlines; xmin += wwidth / nlines * cfmt.breaklimit; - for (s = s.ts_next; s != last ; s = s.ts_next) { + 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 } //fixme: can this occur? - if (s == last) { - if (last) - last = set_nl(last) - return last + if (s == next) { + if (next) + next = set_nl(next) + return next } /* try to cut on a measure bar */ - cut_here = false; s3 = null - for ( ; s != last; s = s.ts_next) { + for ( ; s != next; s = s.ts_next) { x = s.x if (!x) continue if (x > xmax) break @@ -1839,25 +1928,18 @@ if (!s3 || x - xmid < xmid - s3.x) s3 = s // closest bar after middle break } - /* if a bar, cut here */ - if (s3) { - s = s3; - cut_here = true - } - - /* try to avoid to cut a beam or a tuplet */ - if (!cut_here) { + // no bar, try to avoid to cut a beam or a tuplet */ + if (!s3) { 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 - s3 = null - for ( ; s != last; s = s.ts_next) { + for ( ; s != next; s = s.ts_next) { if (s.beam_st) beam++ if (s.beam_end && beam > 0) beam-- x = s.x @@ -1881,20 +1963,16 @@ if (xmid - s3.x < s.x - xmid) break s3 = s break } - if (s3) { - s = s3; - cut_here = true - } } // cut anyhere - if (!cut_here) { + if (!s3) { s3 = s = s2 - for ( ; s != last; s = s.ts_next) { + for ( ; s != next; s = s.ts_next) { x = s.x if (!x) continue if (s.x < xmid) { s3 = s @@ -1905,27 +1983,32 @@ if (xmid - s3.x < s.x - xmid) break s3 = s break } - s = s3 + } + s = s3 + while (s.ts_next) { + s = s.ts_next + if (s.seqst) + break } if (s.nl) { /* already set here - advance */ error(0, s, "Line split problem - adjust maxshrink and/or breaklimit"); nlines = 2 - for (s = s.ts_next; s != last; s = s.ts_next) { + for (s = s.ts_next; s != next; s = s.ts_next) { if (!s.x) continue if (--nlines <= 0) break } } s = set_nl(s) if (!s - || (last && s.time >= last.time)) + || (next && s.time >= next.time)) break wwidth -= s.x - first.x; indent = 0 } return s @@ -1966,11 +2049,14 @@ for (s2 = s; s2; s2 = s2.ts_next) { if (s2.type != C.BAR || !s2.bar_num || --i > 0) continue - s2.eoln = true; + 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 } } /* cut at explicit end of line, checking the line width */ @@ -1988,27 +2074,28 @@ // set_format(s.subtype, s.param) // break // } // continue // } - if (!s.ts_next) + if (!s.ts_next) { s = null - else if (!s.eoln) + } else if (!s.soln) { continue - else - s.eoln = false + } else { + s.soln = false + 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 - // (s2 may be tsfirst - no ts_prev - when only one - // embedded info in the first line after the first K:) - if (!s2.ts_prev) { - delete s2.nl - continue - } - s = s2.ts_prev; // don't miss an eoln +//--fixme +// s = s2.ts_prev; // don't miss an eoln + s = s2 indent = 0 } //fixme: not usable yet // // restore the page parameters at start of line @@ -2124,11 +2211,11 @@ if (voice_tb.length == 1) break case C.NOTE: delta = staff_d[st] if (delta - && !s.p_v.key.k_drum) { + && !s.p_v.ckey.k_drum) { for (m = s.nhd; m >= 0; m--) { note = s.notes[m]; if (!note.opit) note.opit = note.pit; note.pit += delta @@ -2137,11 +2224,11 @@ break case C.GRACE: for (g = s.extra; g; g = g.next) { delta = staff_d[st] if (delta - && !s.p_v.key.k_drum) { + && !s.p_v.ckey.k_drum) { for (m = 0; m <= g.nhd; m++) { note = g.notes[m] if (!note.opit) note.opit = note.pit note.pit += delta @@ -2150,10 +2237,138 @@ } break } } } + +// expand the multi-rests as needed +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, + 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) + while (!s2.bar_num) // get the bar number + s2 = s2.ts_prev + nbar = s2.bar_num - s.nmes + + 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.time = tim + while (s3.time < tim) + s3 = s3.ts_next // bar at end of measure + while (s3 && s3.v < s.v && s3.type == C.BAR) + s3 = s3.ts_next // keep in order + if (s3) { + lktsym(s2, s3) + if (s3.type == C.BAR) + delete s3.bar_num + } else { + s3 = s + while (s3.ts_next) + s3 = s3.ts_next + s3.ts_next = s2 + s2.ts_prev = s3 + s2.ts_next = null + } + nbar++ + if (s2.seqst) { + s2.bar_num = nbar + s4 = s2.ts_next + } else { + delete s2.bar_num + s4 = s2.ts_prev + } + s2.bar_type = s4.bar_type || "|" + if (s4.bar_num && !s4.seqst) + delete s4.bar_num + + // add the rest + s4 = clone(s) + delete s4.a_dd + delete s4.soln + if (s2.next) { + s4.next = s2.next + s4.next.prev = s4 + } else { + s4.next = null + } + s2.next = s4 + s4.prev = s2 + s4.time = tim + + while (s3 && !s3.dur && s3.time == tim) + s3 = s3.ts_next + while (s3 && s3.v < s.v) { + s3 = s3.ts_next // keep in order + if (s3 && s3.seqst) + break + } + if (s3) { + lktsym(s4, s3) + } else { + s3 = s + while (s3.ts_next) + s3 = s3.ts_next + s3.ts_next = s4 + s4.ts_prev = s3 + s4.ts_next = null + } + + tim += dur + s = s3 = s4 + } + } // mexp() + + for (s = tsfirst; s; s = s.ts_next) { + if (s.type != C.MREST) + continue + if (!s.seqst && w_tb[s.ts_prev.type]) { + s2 = s + } else { + s2 = s.ts_next + while (!s2.seqst) { + if (s2.type != C.MREST + || s2.nmes != s.nmes) + break + s2 = s2.ts_next + } + } + if (!s2.seqst) { + while (s.type == C.MREST) { + mexp(s) + s = s.ts_next + } + } else { + s = s2.ts_prev + } + } +} // 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; @@ -2174,11 +2389,11 @@ } continue } if (s.notes[0].pit < min) min = s.notes[0].pit - else if (s.notes[s.nhd].pit > max) + if (s.notes[s.nhd].pit > max) max = s.notes[s.nhd].pit } if (min >= 19 /* upper than 'F' */ || (min >= 13 && clef_type_start != 'b')) /* or 'G,' */ @@ -2324,12 +2539,11 @@ p_voice = voice_tb[v] if (!sy.voices[v]) continue st = sy.voices[v].st if (!sy.voices[v].second) { // main voices - if (p_voice.staffnonote != undefined) - sy.staves[st].staffnonote = p_voice.staffnonote + sy.staves[st].staffnonote = p_voice.staffnonote if (p_voice.staffscale) sy.staves[st].staffscale = p_voice.staffscale if (sy.voices[v].sep) sy.staves[st].sep = sy.voices[v].sep if (sy.voices[v].maxsep) @@ -2370,12 +2584,11 @@ if (!sy.voices[v]) continue p_voice = voice_tb[v]; st = sy.voices[v].st if (!sy.voices[v].second) { - if (p_voice.staffnonote != undefined) - sy.staves[st].staffnonote = p_voice.staffnonote + sy.staves[st].staffnonote = p_voice.staffnonote if (p_voice.staffscale) sy.staves[st].staffscale = p_voice.staffscale if (sy.voices[v].sep) sy.staves[st].sep = sy.voices[v].sep if (sy.voices[v].maxsep) @@ -2580,11 +2793,11 @@ break case C.GRACE: for (g = s.extra; g; g = g.next) { delta = staff_delta[g.st] if (delta - && !s.p_v.key.k_drum) { + && !s.p_v.ckey.k_drum) { for (m = 0; m <= g.nhd; m++) { note = g.notes[m]; note.opit = note.pit note.pit += delta } @@ -2618,21 +2831,22 @@ } // fall thru case C.NOTE: delta = staff_delta[st] if (delta - && !s.p_v.key.k_drum) { + && !s.p_v.ckey.k_drum) { for (m = s.nhd; m >= 0; m--) { note = s.notes[m] note.opit = note.pit note.pit += delta } } - if (s.type == C.NOTE) { - s.ymx = 3 * (s.notes[s.nhd].pit - 18) + 4; - s.ymn = 3 * (s.notes[0].pit - 18) - 4; - } else { +// if (s.type == C.NOTE) { +// s.ymx = 3 * (s.notes[s.nhd].pit - 18) + 4; +// s.ymn = 3 * (s.notes[0].pit - 18) - 4; +// } else { + if (s.type == C.REST) { s.y = (((s.notes[0].pit - 18) / 2) | 0) * 6; s.ymx = s.y + rest_sp[5 - s.nflags][0]; s.ymn = s.y - rest_sp[5 - s.nflags][1] } if (s.dur < dur) @@ -2813,11 +3027,11 @@ /* 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 - > st_v[i + 1].ymx) + >= st_v[i + 1].ymx) s.multi = -1; /* special case for unison */ t = s.ts_next //fixme: pb with ../lacerda/evol-7.5.5.abc @@ -3004,22 +3218,17 @@ s.prev = p_v.last_sym } p_v.last_sym = s; lktsym(s, last_s) - if (s.ts_prev.type != s.type) - s.seqst = true - if (last_s.type == s.type && s.v != last_s.v) { - delete last_s.seqst; - last_s.shrink = 0 - } } /* -- init the symbols at start of a music line -- */ function init_music_line() { - var p_voice, s, s2, s3, last_s, v, st, shr, shrmx, - nv = voice_tb.length + var p_voice, s, s1, s2, s3, last_s, v, st, shr, shrmx, shl, + shlp, p_st, top, + nv = voice_tb.length /* initialize the voices */ for (v = 0; v < nv; v++) { if (!cur_sy.voices[v]) continue @@ -3030,28 +3239,54 @@ /* move the voice to a printed staff */ st = cur_sy.voices[v].st while (st < nstaff && !cur_sy.st_print[st]) st++; p_voice.st = st + if (!p_voice.second) { + staff_tb[st].key = p_voice.ckey + staff_tb[st].meter = p_voice.meter + } + } + + // move the first clefs, key signatures and time signatures + // to the staves + s = tsfirst + while (s) { + switch (s.type) { + case C.CLEF: + case C.KEY: + case C.METER: + switch (s.type) { + case C.CLEF: + staff_tb[s.st].clef = s + break + case C.KEY: + s.p_v.ckey = s + break + case C.METER: + s.p_v.meter = s + insert_meter |= 1 + break + } + unlksym(s) + // fall thru + case C.PART: + case C.TEMPO: + case C.BLOCK: + case C.REMARK: + s = s.ts_next + continue + } + break } - /* add a clef at start of the main voices */ + // generate the starting clefs, key signatures and time signatures + + // add a clef at start of the main voices last_s = tsfirst - while (last_s && last_s.type == C.CLEF) { // move the starting clefs - v = last_s.v - if (cur_sy.voices[v] - && !cur_sy.voices[v].second) { - delete last_s.clef_small; /* normal clef */ - p_voice = last_s.p_v; - p_voice.last_sym = p_voice.sym = last_s - } - last_s = last_s.ts_next - } for (v = 0; v < nv; v++) { p_voice = voice_tb[v] - if (p_voice.sym && p_voice.sym.type == C.CLEF) - continue if (!cur_sy.voices[v] || (cur_sy.voices[v].second && !p_voice.bar_start)) // needed for correct linkage continue st = cur_sy.voices[v].st @@ -3065,20 +3300,18 @@ s.time = tsfirst.time; s.prev = null; s.next = p_voice.sym if (s.next) s.next.prev = s; - p_voice.sym = s; - p_voice.last_sym = s; + p_voice.sym = p_voice.last_sym = s s.ts_next = last_s; if (last_s) s.ts_prev = last_s.ts_prev else s.ts_prev = null if (!s.ts_prev) { tsfirst = s; - s.seqst = true } else { s.ts_prev.ts_next = s delete s.seqst } if (last_s) { @@ -3098,20 +3331,15 @@ if (!cur_sy.voices[v] || cur_sy.voices[v].second || !cur_sy.st_print[cur_sy.voices[v].st]) continue p_voice = voice_tb[v] - if (last_s && last_s.v == v && last_s.type == C.KEY) { - p_voice.last_sym = last_s; - last_s.k_old_sf = last_s.k_sf; // no key cancel - last_s = last_s.ts_next - continue - } - s2 = p_voice.key + s2 = p_voice.ckey if (s2.k_sf || s2.k_a_acc) { s = clone(s2) new_sym(s, p_voice, last_s) + delete s.invis s.k_old_sf = s2.k_sf // no key cancel } } /* add time signature (meter) if needed */ @@ -3122,15 +3350,10 @@ if (!cur_sy.voices[v] || cur_sy.voices[v].second || !cur_sy.st_print[cur_sy.voices[v].st] || !s2.a_meter.length) continue - if (last_s && last_s.v == v && last_s.type == C.METER) { - p_voice.last_sym = last_s; - last_s = last_s.ts_next - continue - } s = clone(s2) new_sym(s, p_voice, last_s) } insert_meter &= ~1 // no meter any more } @@ -3151,70 +3374,106 @@ new_sym(s, p_voice, last_s) p_voice.sls = [] } } - /* add bar if needed (for repeat bracket) */ + // add a bar for the continuation of repeat brackets 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 && last_s.type == C.BAR) { - p_voice.last_sym = last_s; - last_s = last_s.ts_next - continue + 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 + } + break + } } if (!s2) continue if (!cur_sy.voices[v] || !cur_sy.st_print[cur_sy.voices[v].st]) continue - new_sym(s2, p_voice, last_s) + 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) + } } // compute the spacing of the added symbols self.set_pitch(last_s); s = tsfirst + s.seqst = true + shlp = 0 while (1) { s2 = s; - shrmx = 0 + shl = shrmx = 0 do { self.set_width(s); - shr = s.wl - if (s.prev) - shr += s.prev.wr + shr = s.wl + shlp if (shr > shrmx) shrmx = shr; + if (s.wr > shl) + shl = s.wr // left width for next symbol s = s.ts_next } while (s != last_s && !s.seqst); s2.shrink = shrmx; s2.space = 0 if (s == last_s) break + shlp = shl } // update the spacing before the first old time sequence - if (!s) - return shr = 0 do { self.set_width(s) if (shr < s.wl) shr = s.wl; s = s.ts_next } while (s && !s.seqst); - last_s.shrink = s2.wr + shr + last_s.shrink = shl + shr last_s.space = 0 -} +} // init_music_line() + +// check if the tune ends on a measure bar +function check_end_bar() { + var s2, + s = tsfirst + while (s.ts_next) + 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() /* -- set a pitch in all symbols and the start/stop of the beams -- */ // and sort the pitches in the chords +// and build the chord symbols / annotations +// this function is called only once per tune function set_words(p_voice) { var s, s2, nflags, lastnote, res, start_flag = true, pitch = 127 /* no note */ @@ -3223,21 +3482,22 @@ pitch = s.notes[0].pit break } } for (s = p_voice.sym; s; s = s.next) { + if (s.a_gch) + self.gch_build(s) switch (s.type) { case C.MREST: start_flag = true break case C.BAR: s.bar_type = bar_cnv(s.bar_type) if (!s.beam_on) start_flag = true if (!s.next && s.prev -// && s.prev.type == C.NOTE -// && s.prev.dur >= C.BLEN * 2) + && !s.invis && s.prev.head == C.OVALBARS) s.prev.head = C.SQUARE break case C.GRACE: for (s2 = s.extra; s2; s2 = s2.next) @@ -3281,11 +3541,12 @@ nflags = s.nflags if (s.ntrem) nflags += s.ntrem - if (s.type == C.REST && s.beam_end) { + if (s.type == C.REST && s.beam_end + && !s.beam_on) { s.beam_end = false; start_flag = true } if (start_flag || nflags <= 0) { @@ -3294,18 +3555,18 @@ lastnote = null } if (nflags <= 0) { s.beam_st = true; s.beam_end = true - } else if (s.type == C.NOTE) { + } else if (s.type == C.NOTE || s.beam_on) { s.beam_st = true; start_flag = false } } if (s.beam_end) start_flag = true - if (s.type == C.NOTE) + if (s.type == C.NOTE || s.beam_on) lastnote = s break } if (s.type == C.NOTE) { if (s.nhd) @@ -3366,43 +3627,51 @@ } } } /* -- initialize the generator -- */ -/* this function is called only once per tune */ +// this function is called only once per tune var delpit = [0, -7, -14, 0] function set_global() { - var p_voice, st, v, nv, sy + var p_voice, v, + nv = voice_tb.length, + sy = cur_sy, + st = sy.nstaff /* get the max number of staves */ - sy = cur_sy; - st = sy.nstaff; -// sy.st_print = new Uint8Array(sy.staves.length) while (1) { sy = sy.next if (!sy) break -// sy.st_print = new Uint8Array(sy.staves.length) if (sy.nstaff > st) st = sy.nstaff } nstaff = st; + + // there must be a bar at end of tune + check_end_bar() /* set the pitches, the words (beams) and the repeat brackets */ - nv = voice_tb.length for (v = 0; v < nv; v++) { p_voice = voice_tb[v]; set_words(p_voice) + p_voice.ckey = p_voice.key // starting key // (test removed because v.second may change after %%staves) // if (!p_voice.second && !p_voice.norepbra) set_rb(p_voice) } /* set the staff of the floating voices */ - set_float(); + if (nv > 1) { + set_float() + + // expand the multi-rests as needed + if (glovar.mrest_p) + mrest_expand() + } if (glovar.ottava && cfmt.sound != "play") set_ottava(); // set the clefs and adjust the pitches of all symbol @@ -3431,11 +3700,11 @@ set_font("voice"); font = gene.curfont } i = 0 while (1) { - j = p.indexOf("\\n", i) + j = p.indexOf("\n", i) if (j < 0) w = strwh(p.slice(i)) else w = strwh(p.slice(i, j)) w = w[0] @@ -3719,15 +3988,17 @@ return true } /* handle unison with different accidentals */ function unison_acc(s1, s2, i1, i2) { - var m, d + var m, d, acc - if (!s2.notes[i2].acc) { + acc = s2.notes[i2].acc + if (!acc) { d = w_note[s2.head] * 2 + s2.xmx + s1.notes[i1].shac + 2 - if (s1.notes[i1].micro) + acc = s1.notes[i1].acc + if (typeof acc != "number") // microtone d += 2 if (s2.dots) d += 6 for (m = 0; m <= s1.nhd; m++) { s1.notes[m].shhd += d; @@ -3734,11 +4005,11 @@ s1.notes[m].shac -= d } s1.xmx += d } else { d = w_note[s1.head] * 2 + s1.xmx + s2.notes[i2].shac + 2 - if (s2.notes[i2].micro) + if (typeof acc != "number") // microtone d += 2 if (s1.dots) d += 6 for (m = 0; m <= s2.nhd; m++) { s2.notes[m].shhd += d; @@ -3976,18 +4247,19 @@ d = -10 for (i = 0; i < MAXPIT; i++) { if (left2[i] + right1[i] > d) d = left2[i] + right1[i] } - if (d < -3) { // no clash if no dots clash - if (!s1.dots || !s2.dots - || !s2.dot_low - || s1.stem > 0 || s2.stem < 0 - || s1.notes[s1.nhd].pit + 2 != s2.notes[0].pit - || (s2.notes[0].pit & 1)) - continue - } + + if (d < -3 // no clash if no dots clash + && ((s2.notes[0].pit & 1) + || !(s1.dots || s2.dots) + || (!(s1.notes[s1.nhd].pit == s2.notes[0].pit + 2 + && s1.dot_low) + && !(s1.notes[s1.nhd].pit + 2 == s2.notes[0].pit + && s2.dot_low)))) + continue right2 = set_right(s2); left1 = set_left(s1) if (s3) { right3 = set_left(s3) @@ -4012,10 +4284,20 @@ i1 = s1.nhd; i2 = s2.nhd while (1) { dp = s1.notes[i1].pit - s2.notes[i2].pit switch (dp) { + case 2: + if (!(s1.notes[i1].pit & 1)) + s1.dot_low = false + break + case 1: + if (s1.notes[i1].pit & 1) + 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) { t = -1 break @@ -4028,27 +4310,18 @@ if (s1.dots && s2.dots && (s1.notes[i1].pit & 1)) t = 1 break case -1: - if (s1.dots && s2.dots) { - if (s1.notes[i1].pit & 1) { - s1.dot_low = false; - s2.dot_low = false - } else { - s1.dot_low = true; - s2.dot_low = true - } - } + if (s1.notes[i1].pit & 1) + s2.dot_low = false + else + s1.dot_low = true break case -2: - if (s1.dots && s2.dots - && !(s1.notes[i1].pit & 1)) { - s1.dot_low = false; + if (!(s1.notes[i1].pit & 1)) s2.dot_low = false - break - } break } if (t < 0) break if (dp >= 0) { @@ -4267,12 +4540,10 @@ s.ys = ymx } else { s.ys = ymn; s.y = ymx } - if (nflags == -4) /* if longa */ - ymn -= 6; s.ymx = ymx + 4; s.ymn = ymn - 4 } else if (s.stem >= 0) { if (s.notes[s.nhd].pit > 26 && (nflags <= 0 @@ -4309,122 +4580,10 @@ s.ymx = ymx + 4 } } } -/* -- split up unsuitable bars at end of staff -- */ -// return true if the bar type has changed -function check_bar(s) { - var bar_type, i, b1, b2, s_bs, s2, - p_voice = s.p_v - - /* search the last bar */ - while (s.type == C.CLEF || s.type == C.KEY || s.type == C.METER) { - if (s.type == C.METER - && s.time > p_voice.sym.time) /* if not empty voice */ - insert_meter |= 1; /* meter in the next line */ - s = s.prev - if (!s) - return - } - if (s.type != C.BAR) - return - - if (s.text != undefined) { // if repeat bar - p_voice.bar_start = clone(s); - p_voice.bar_start.bar_type = "" - delete s.text - delete s.a_gch -// return - } - bar_type = s.bar_type - if (bar_type == ":") - return - if (bar_type.slice(-1) != ':') // if not left repeat bar - return - - // add a left repeat bar in the next music line - s2 = p_voice.s_next - while (1) { - switch (s2.type) { - case C.BAR: - case C.GRACE: - case C.NOTE: - case C.REST: - break - default: - s2 = s2.next - if (!s2) - return - } - break - } - if (s2.type == C.BAR) { - s_bs = s2 // bar start - } else { - s_bs = clone(s) - s_bs.next = s2 - s_bs.prev = s2.prev - if (s2 == p_voice.s_next) - p_voice.s_next = s_bs - else - s2.prev.next = s_bs - s2.prev = s_bs - - while (!s2.seqst) - s2 = s2.ts_prev - s_bs.ts_next = s2 - s_bs.ts_prev = s2.ts_prev - if (s2 == tsnext) - tsnext = s_bs - else - s2.ts_prev.ts_next = s_bs - s2.ts_prev = s_bs - delete s_bs.seqst - if (s_bs == tsnext || s2.ts_prev.type != C.BAR) { - if (s2.seqst) { - s_bs.seqst = true - } else { - s2.seqst = true - s2.shrink = s_bs.wr + s2.wl - s2.space = 0 - } - } - } - - if (bar_type[0] != ':') { // 'xx:' (not ':xx:') - if (bar_type == "||:") { - s_bs.bar_type = "[|:" - s.bar_type = "||" - return true - } - s_bs.bar_type = bar_type - if (s.prev && s.prev.type == C.BAR) - unlksym(s) - else - s.bar_type = "|" - return - } - - // ':xx:' -> ':x|]' and '[|x:' - i = 0 - while (bar_type[i] == ':') - i++ - if (i < bar_type.length) { - s.bar_type = bar_type.slice(0, i) + '|]'; - i = bar_type.length - 1 - while (bar_type[i] == ':') - i--; - s_bs.bar_type = '[|' + bar_type.slice(i + 1) - } else { - i = (bar_type.length / 2) |0; // '::::' ! - s.bar_type = bar_type.slice(0, i) + '|]'; - s_bs.bar_type = '[|' + bar_type.slice(i) - } - return true -} - /* -- move the symbols of an empty staff to the next one -- */ function sym_staff_move(st) { for (var s = tsfirst; s; s = s.ts_next) { if (s.nl) break @@ -4437,29 +4596,68 @@ } // generate a block symbol var blocks = [] // array of delayed block symbols -function block_gen(s) { +// (possible hook) +Abc.prototype.block_gen = function(s) { switch (s.subtype) { case "leftmargin": case "rightmargin": case "pagescale": case "pagewidth": case "scale": case "staffwidth": - svg_flush(); + blk_flush() self.set_format(s.subtype, s.param) break + case "mc_start": // multicol start + multicol = { + posy: posy, + maxy: posy, + lmarg: cfmt.leftmargin, + rmarg: cfmt.rightmargin + } + break + case "mc_new": // multicol new + if (!multicol) { + error(1, s, "%%multicol new without start") + break + } + if (posy > multicol.maxy) + multicol.maxy = posy + cfmt.leftmargin = multicol.lmarg + cfmt.rightmargin = multicol.rmarg + img.chg = true + set_page() + posy = multicol.posy + break + case "mc_end": // multicol end + if (!multicol) { + error(1, s, "%%multicol new without start") + break + } + if (posy < multicol.maxy) + posy = multicol.maxy + cfmt.leftmargin = multicol.lmarg + cfmt.rightmargin = multicol.rmarg + multicol = undefined + blk_flush() + img.chg = true + set_page() + break case "ml": - svg_flush(); + blk_flush() user.img_out(s.text) break case "newpage": - blk_flush(); - block.newpage = true; - blk_out() + svg_flush() + if (!user.page_format) + break + if (blkdiv < 0) // split the tune + user.img_out('') + blkdiv = 2 // start the next SVG in a new page break case "sep": set_page(); vskip(s.sk1); output += '\n'; vskip(s.sk2); break case "text": + set_font(s.font) write_text(s.text, s.opt) break case "title": write_title(s.text, true) break case "vskip": vskip(s.sk); -// blk_out() break } } /* -- define the start and end of a piece of tune -- */ /* tsnext becomes the beginning of the next line */ function set_piece() { - var s, last, p_voice, st, v, nst, nv, tmp, - non_empty = [], - non_empty_gl = [], - sy = cur_sy + var s, last, p_voice, st, v, nv, tmp, + non_empty = [], + non_empty_gl = [], + sy = cur_sy function reset_staff(st) { var p_staff = staff_tb[st], sy_staff = sy.staves[st] @@ -4530,10 +4728,17 @@ function set_top_bot() { var st, p_staff, i, j, l for (st = 0; st <= nstaff; st++) { p_staff = staff_tb[st] + + // ledger lines + // index = line number + // values = [x symbol, x start, x stop] + p_staff.hlu = [] // above the staff + p_staff.hld = [] // under the staff + if (!non_empty_gl[st]) { p_staff.botbar = p_staff.topbar = 0 continue } l = p_staff.stafflines.length; @@ -4574,64 +4779,74 @@ break } } } } // set_top_bot() + + // remove the staff system at start of line + if (tsfirst.type == C.STAVES) { + s = tsfirst + tsfirst = tsfirst.ts_next + tsfirst.ts_prev = null + if (s.seqst) + tsfirst.seqst = true + s.p_v.sym = s.next + if (s.next) + s.next.prev = null + } /* reset the staves */ - nstaff = nst = sy.nstaff - for (st = 0; st <= nst; st++) + nstaff = sy.nstaff + for (st = 0; st <= nstaff; st++) reset_staff(st); /* * search the next end of line, * and mark the empty staves */ for (s = tsfirst; s; s = s.ts_next) { - if (s.nl) { -//fixme: not useful -// // delay the next block symbols -// while (s && s.type == C.BLOCK) { -// blocks.push(s); -// unlksym(s); -// s = s.ts_next -// } + if (s.nl) break - } - if (!s.ts_next) - last = s // keep the last symbol of the tune switch (s.type) { case C.STAVES: set_brace(); sy.st_print = new Uint8Array(non_empty); sy = s.sy; - nst = sy.nstaff - if (nstaff < nst) { - for (st = nstaff + 1; st <= nst; st++) - reset_staff(st); - nstaff = nst - } + while (nstaff < sy.nstaff) + reset_staff(++nstaff) non_empty = [] continue // the block symbols will be treated after music line generation case C.BLOCK: - if (!s.play) + if (!s.play) { blocks.push(s) + unlksym(s) + } continue } st = s.st + if (st > nstaff) { + switch (s.type) { + case C.CLEF: + staff_tb[st].clef = s // clef warning/change for new staff + break + case C.KEY: + s.p_v.ckey = s + break +//useless ? + case C.METER: + s.p_v.meter = s + break + } + unlksym(s) + continue + } if (non_empty[st]) continue switch (s.type) { default: - continue - case C.CLEF: - if (st > nstaff) { // if clef warning/change for new staff - staff_tb[st].clef = s; - unlksym(s) - } continue case C.BAR: if (s.bar_mrep || sy.staves[st].staffnonote > 1) break @@ -4644,11 +4859,11 @@ case C.MREST: if (sy.staves[st].staffnonote > 1) break if (s.invis) continue - if (sy.staves[st].staffnonote != 0 + if (sy.staves[st].staffnonote || s.type == C.NOTE) break continue } non_empty_gl[st] = non_empty[st] = true @@ -4655,12 +4870,10 @@ } tsnext = s; /* set the last empty staves */ set_brace() -// for (st = 0; st <= nstaff; st++) -// sy.st_print[st] = non_empty[st]; sy.st_print = new Uint8Array(non_empty); /* define the offsets of the measure bars */ set_top_bot() @@ -4696,15 +4909,10 @@ && p_voice.sym.time <= tsnext.time) { for (s = tsnext.ts_prev; s; s = s.ts_prev) { if (s.v == v) { p_voice.s_next = s.next; s.next = null; - if (check_bar(s)) { - tmp = s.wl; - self.set_width(s); - s.shrink += s.wl - tmp - } break } } if (s) continue @@ -4711,19 +4919,10 @@ } p_voice.s_next = p_voice.sym; p_voice.sym = null } } - - // if the last symbol is not a bar, add an invisible bar - if (last.type != C.BAR) { - s = add_end_bar(last); - s.space = set_space(s) - if (s.space < s.shrink - && last.type != C.KEY) - s.space = s.shrink - } } /* -- position the symbols along the staff -- */ // (possible hook) Abc.prototype.set_sym_glue = function(width) { @@ -4879,21 +5078,23 @@ } switch (s.type) { case C.NOTE: case C.REST: case C.MREST: + case C.SPACE: set_page() return default: continue case C.STAVES: cur_sy = s.sy - break +// break + continue case C.BLOCK: if (s.play) continue // keep for play - block_gen(s) + self.block_gen(s) break } unlksym(s) if (s.p_v.s_next == s) s.p_v.s_next = s.next @@ -4902,25 +5103,25 @@ } /* -- generate the music -- */ // (possible hook) Abc.prototype.output_music = function() { - var v, lwidth, indent, line_height, ts1st, tslast, p_v + var v, lwidth, indent, line_height, ts1st, tslast, p_v, + nv = voice_tb.length gen_init() if (!tsfirst) return - tunes.push([tsfirst, voice_tb]) // keep tune data for playing set_global() - if (voice_tb.length > 1) /* if many voices */ + if (nv > 1) // if many voices self.set_stem_dir() // set the stems direction in 'multi' - for (v = 0; v < voice_tb.length; v++) + for (v = 0; v < nv; v++) set_beams(voice_tb[v].sym); /* decide on beams */ self.set_stems() // set the stem lengths - if (voice_tb.length > 1) { /* if many voices */ + 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 @@ -4941,11 +5142,11 @@ cut_tune(lwidth, indent) } // save symbol pointers for play ts1st = tsfirst - v = voice_tb.length + v = nv while (--v >= 0) voice_tb[v].osym = voice_tb[v].sym spf_last = 1.2 // last spacing factor while (1) { /* loop per music line */ @@ -4963,17 +5164,14 @@ if (indent) { posx -= indent; insert_meter &= ~2 // no more indentation } while (blocks.length) - block_gen(blocks.shift()) + self.block_gen(blocks.shift()) } - if (cfmt.splittune) - blk_flush() - else - svg_flush() + blk_flush() if (tslast) tslast.ts_next.ts_prev = tslast if (!tsnext) break tsnext.ts_prev.ts_next = // (restore for play) @@ -4990,11 +5188,11 @@ indent = set_indent() } // restore for play tsfirst = ts1st - v = voice_tb.length + v = nv while (--v >= 0) { p_v = voice_tb[v] if (p_v.sym && p_v.s_prev) p_v.sym.prev = p_v.s_prev p_v.sym = p_v.osym Index: core/parse.js ================================================================== --- core/parse.js +++ core/parse.js @@ -1,8 +1,8 @@ // abc2svg - parse.js - ABC parse // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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 @@ -110,15 +110,19 @@ 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 break case '_': s.clef_oct_transp = true case '-': s.clef_octave = -7 + if (!s.clef_oct_transp) + curvoice.snd_oct = -12 // MIDI lower octave break } return s } @@ -131,14 +135,14 @@ pit = [] for (i = 0; i < 2; i++) { note = tmp.buffer[tmp.index] ? parse_acc_pit(tmp) : null if (!note) { if (i != 1 || !score) { - syntax(1, "Bad transpose value") + syntax(1, errs.bad_transp) return } - pit[i] = 23 // 'c' (C5) + pit[i] = 242 // 'c' (C5) } else { pit[i] = abc2svg.pab40(note.pit, note.acc) } } return pit[1] - pit[0] @@ -178,11 +182,11 @@ } // set a new user character (U: or %%user) function set_user(parm) { var k, c, v, - a = parm.match(/(.)[= ]*(\[I:.+\]|".+"|!.+!)$/) + a = parm.match(/(.)[=\s]*(\[I:.+\]|".+"|!.+!)$/) if (!a) { syntax(1, 'Lack of starting [, ! or " in U: / %%user') return } @@ -258,10 +262,12 @@ 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 @@ -302,16 +308,15 @@ } } syntax(1, errs.bad_val, item) break case "octave=": - case "uscale=": // %%microscale val = parseInt(a.shift()) if (isNaN(val)) syntax(1, errs.bad_val, item) else - curvoice[item.slice(0, -1)] = val + curvoice.octave = val break case "cue=": curvoice.scale = a.shift() == 'on' ? .7 : 1 break case "instrument=": @@ -338,12 +343,12 @@ curvoice.nm = a.shift() if (curvoice.nm[0] == '"') curvoice.nm = curvoice.nm.slice(1, -1); curvoice.new_name = true break - case "stem=": - case "pos=": + case "stem=": // compatibility + case "pos=": // from %%pos only if (item == "pos=") item = a.shift().split(' ') else item = ["stm", a.shift()]; val = posval[item[1]] @@ -355,28 +360,39 @@ pos = {} pos[item[0]] = val break case "scale=": // %%voicescale val = parseFloat(a.shift()) - if (isNaN(val) || val < .6 || val > 1.5) + if (isNaN(val) || val < .5 || val > 2) syntax(1, errs.bad_val, "%%voicescale") else curvoice.scale = val break case "score=": + if (cfmt.nedo) { + syntax(1, errs.notransp) + break + } // score=MN // (score=M == score=Mc) item = a.shift() if (cfmt.sound) break - curvoice.transp = get_transp(item) + curvoice.transp = get_interval(item, true) break case "shift=": + if (cfmt.nedo) { + syntax(1, errs.notransp) + break + } curvoice.shift = curvoice.sndsh = get_interval(a.shift()) break case "sound=": - case "transpose=": // (abcMIDI compatibility) + 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()) if (cfmt.sound) @@ -409,10 +425,24 @@ val = parseFloat(a.shift()) if (isNaN(val) || val < .3 || val > 2) syntax(1, "Bad %%staffscale value") else curvoice.staffscale = val + break + case "transpose=": // (abcMIDI compatibility) + if (cfmt.nedo) { + syntax(1, errs.notransp) + break + } + val = get_transp(a.shift()) + if (val == undefined) { + syntax(1, errs.bad_transp) + } else { + curvoice.sndtran = val + if (cfmt.sound) + curvoice.transp = val + } break default: switch (item.slice(0, 4)) { case "treb": case "bass": @@ -482,20 +512,18 @@ var i, clef, key_end, c, tmp, exp, sf = "FCGDAEB".indexOf(param[0]) - 1, mode = 0, s = { type: C.KEY, - dur:0 + dur: 0 } // set the accidentals when K: with modified accidentals function set_k_acc(s, sf) { var i, j, n, nacc, p_acc, accs = [], - pits = [], - m_n = [], - m_d = [] + pits = [] if (sf > 0) { for (nacc = 0; nacc < sf; nacc++) { accs[nacc] = 1; // sharp pits[nacc] = [26, 23, 27, 24, 21, 25, 22][nacc] @@ -510,40 +538,25 @@ for (i = 0; i < n; i++) { p_acc = s.k_a_acc[i] for (j = 0; j < nacc; j++) { if (pits[j] == p_acc.pit) { accs[j] = p_acc.acc - if (p_acc.micro_n) { - m_n[j] = p_acc.micro_n; - m_d[j] = p_acc.micro_d - } break } } if (j == nacc) { accs[j] = p_acc.acc; pits[j] = p_acc.pit - if (p_acc.micro_n) { - m_n[j] = p_acc.micro_n; - m_d[j] = p_acc.micro_d - } nacc++ } } for (i = 0; i < nacc; i++) { p_acc = s.k_a_acc[i] if (!p_acc) p_acc = s.k_a_acc[i] = {} p_acc.acc = accs[i]; p_acc.pit = pits[i] - if (m_n[i]) { - p_acc.micro_n = m_n[i]; - p_acc.micro_d = m_d[i] - } else { - delete p_acc.micro_n - delete p_acc.micro_d - } } } // set_k_acc() // code of new_key() set_ref(s); @@ -551,22 +564,41 @@ // tonic i = 1 if (sf < -1) { switch (param[0]) { case 'H': // bagpipe - switch (param[1]) { - case 'P': - case 'p': - s.k_bagpipe = param[1]; - sf = param[1] == 'P' ? 0 : 2; - i++ - break - default: + 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 + // 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 + // 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, + 8.19, 9.39, 10.51, 11.62, 12.55, 0, + 10.51, 11.62, 12.55, + 2.37, 2.37 + ]) break case 'P': syntax(1, "K:P is deprecated"); sf = 0; s.k_drum = true; @@ -579,10 +611,12 @@ i = 4 break } // fall thru default: + s.k_map = [] + s.k_mode = 0 return [s, info_split(param)] } } if (!key_end) { @@ -855,10 +889,11 @@ } /* Q: tempo */ function new_tempo(text) { var i, c, d, nd, + txt = text, // (for info.Q) s = { type: C.TEMPO, dur: 0 } @@ -879,11 +914,11 @@ } // get_nd() set_ref(s) if (cfmt.writefields.indexOf('Q') < 0) - s.del = true // don't display + s.invis = true // don't display /* string before */ if (text[0] == '"') { c = text.match(/"([^"]*)"/) // " if (!c) { @@ -938,26 +973,29 @@ } } if (parse.state != 3) { if (parse.state == 1) { // tune header - info.Q = text; + info.Q = txt glovar.tempo = s return } goto_tune() } - if (curvoice.v == par_sy.top_voice) { /* tempo only for first voice */ - sym_link(s) - if (glovar.tempo && curvoice.time == 0) - glovar.tempo.del = true - } + sym_link(s) + if (glovar.tempo && curvoice.time == 0) + glovar.tempo.invis = true } // treat the information fields which may embedded function do_info(info_type, text) { var s, d1, d2, a, vid + + // skip this line if the current voice is ignored + if (curvoice && curvoice.ignore + && info_type != 'V') + return switch (info_type) { // info fields in any state case 'I': @@ -971,17 +1009,13 @@ if (a) { d1 = Number(a[1]) if (!d1 || (d1 & (d1 - 1)) != 0) break d1 = C.BLEN / d1 - if (a[2]) { + if (a[2]) { // if '=' d2 = Number(a[4]) - if (!d2 || (d2 & (d2 - 1)) != 0) { - d2 = 0 - break - } - d2 = Number(a[3]) / d2 * C.BLEN + d2 = d2 ? Number(a[3]) / d2 * C.BLEN : 0 } else { d2 = d1 } } else if (text == "auto") { d1 = d2 = -1 @@ -1012,44 +1046,28 @@ info.P = text break } if (parse.state == 2) goto_tune() - if (cfmt.writefields.indexOf(info_type) < 0) - break s = { type: C.PART, text: text, dur: 0 } - - /* - * If not in the main voice, then, - * if the voices are synchronized and no P: yet in the main voice, - * the misplaced P: goes into the main voice. - */ - var p_voice = voice_tb[par_sy.top_voice] - if (curvoice.v != p_voice.v) { - if (curvoice.time != p_voice.time) - break - if (p_voice.last_sym && p_voice.last_sym.type == C.PART) - break // already a P: - var voice_sav = curvoice; - curvoice = p_voice; - sym_link(s); - curvoice = voice_sav - } else { - sym_link(s) - } + if (cfmt.writefields.indexOf(info_type) < 0) + s.invis = true + sym_link(s) break case 'Q': if (parse.state == 0) break new_tempo(text) break case 'V': get_voice(text) + if (parse.state == 3) + curvoice.ignore = !par_sy.voices[curvoice.v] break // key signature at end of tune header or in tune body case 'K': if (!parse.state) // ignore if in file header @@ -1136,11 +1154,11 @@ } curvoice.time = s.time = time } /* -- parse a bar -- */ -function new_bar() { +function new_bar(dotted) { var s2, c, bar_type, line = parse.line, s = { type: C.BAR, fname: parse.fname, @@ -1147,10 +1165,12 @@ 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 @@ -1177,13 +1197,13 @@ if (curvoice.tie_s) curvoice.tie_s.tie_s = s } } - // set the guitar chord and the decorations + // set the annotations and the decorations if (a_gch) - self.gch_build(s) + csan_add(s) if (a_dcn) { deco_cnv(a_dcn, s); a_dcn = null } @@ -1206,22 +1226,18 @@ parse.ottava = [] } /* if the last element is '[', it may start * a chord or an embedded header */ - switch (bar_type.slice(-1)) { - case '[': - if (/[0-9" ]/.test(c)) // " - break + if (bar_type.slice(-1) == '[' + && !(/[0-9" ]/.test(c))) { // " bar_type = bar_type.slice(0, -1); line.index--; c = '[' - break - case ':': // left repeat - s.rbstop = 2 // with bracket end - break } + if (bar_type.slice(-1) == ':') // left repeat + s.rbstop = 2 // with bracket end // check if repeat bar if (c > '0' && c <= '9') { s.text = c while (1) { @@ -1266,11 +1282,12 @@ && !curvoice.second) s.norepbra = true // handle the accidentals (ties and repeat) if (s.text) { - if (bar_type.slice(-1) == '[') + if (bar_type.slice(-1) == '[' + && bar_type != '[') bar_type = bar_type.slice(0, -1) if (s.text[0] == '1') { if (curvoice.tie_s) curvoice.tie_s_rep = curvoice.tie_s if (curvoice.acc_tie) @@ -1290,28 +1307,26 @@ } if (curvoice.acc_tie_rep) curvoice.acc_tie = curvoice.acc_tie_rep.slice() } } - curvoice.acc = [] // no accidental anymore - if (curvoice.ulen < 0) // L:auto adjust_dur(s); - s2 = curvoice.last_sym - if (s2 && s2.time == curvoice.time) { - if (bar_type == "[" - || bar_type == "|:") { + if ((bar_type == "[" || bar_type == "|:") + && !curvoice.eoln) { + 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 @@ -1403,16 +1418,20 @@ sym_link(s2); s2.st = curvoice.st delete s.text; s.rbstart = 0 } + + if (!s.bar_dotted && !s.invis) + curvoice.acc = [] // no accidental anymore } // parse %%staves / %%score // return an array of [vid, flags] / null function parse_staves(p) { - var v, vid, + var v, vid, + vids = {}, a_vf = [], err = false, flags = 0, brace = 0, bracket = 0, @@ -1522,11 +1541,17 @@ flags |= STOP_BAR continue } break } - a_vf.push([vid, flags]); + if (vids[vid]) { + syntax(1, "Double voice in %%score") +// err = true + } else { + vids[vid] = true + a_vf.push([vid, flags]) + } flags = 0 if (!e) break a.unshift(e) break @@ -1543,11 +1568,11 @@ // split an info string function info_split(text) { if (!text) return [] - var a = text.match(/=?[^\s"=]+=?|".+?"/g) // " + var a = text.match(/[^\s"=]+=?|"[^"]+"/g) // " if (!a) { //fixme: bad error text syntax(1, "Unterminated string") return [] } @@ -1573,12 +1598,12 @@ return [num, den] } // parse the note accidental and pitch function parse_acc_pit(line) { - var note, acc, micro_n, micro_d, pit, nd, - c = line.char() + var note, acc, pit, d, nd, + c = line.char() // optional accidental switch (c) { case '^': c = line.next_char() @@ -1603,20 +1628,23 @@ } break } /* look for microtone value */ - if (acc && acc != 3 && (c >= '1' && c <= '9') - || c == '/') { // compatibility + if (acc == 1 || acc == -1) { + if ((c >= '1' && c <= '9') + || c == '/') { // shortcut nd = parse_dur(line); - micro_n = nd[0]; - micro_d = nd[1] - if (micro_d == 1) - micro_d = curvoice ? curvoice.uscale : 1 - else - micro_d *= 2; // 1/2 tone fraction -> tone fraction + if (acc < 0) + nd[0] = -nd[0] + if (cfmt.nedo && nd[1] == 1) { + nd[0] *= 12 + nd[1] *= cfmt.nedo + } + acc = nd c = line.char() + } } /* get the pitch */ pit = ntb.indexOf(c) + 16; c = line.next_char() @@ -1637,60 +1665,59 @@ note = { pit: pit, shhd: 0, shac: 0 } - if (acc) { + if (acc) note.acc = acc - if (micro_n) { - note.micro_n = micro_n; - note.micro_d = micro_d - } - } return note } -/* set the mapping of a note */ +// return the mapping of a note // // The global 'maps' object is indexed by the map name. // Its content is an object ('map') indexed from the map type: -// - normal = b40 pitch -// - octave = 'o' + b40 pitch in C..B interval +// - normal = ABC note +// - octave = 'o' + ABC note in C..B interval // - key = 'k' + scale index // - all = 'all' // The 'map' is stored in the note. It is an array of // [0] array of heads (glyph names) // [1] print (note) // [2] color -// [3] play (b40) -function set_map(note) { - var map = maps[curvoice.map], // never null - n = note.b40, - nn = n.toString() +// [3] play (note) +function set_map(note, acc) { + var pit = note.pit, + nn = not2abc(pit, acc), + map = maps[curvoice.map] // never null if (!map[nn]) { - nn = 'o' + (n % 40).toString() // octave + nn = 'o' + nn.replace(/[',]+/, '') // ' octave if (!map[nn]) { - n += 2 - curvoice.ckey.k_b40 // key - nn = 'k' + (abc2svg.b40_p[n % 40]).toString() + nn = 'k' + ntb[(pit + 75 - + curvoice.ckey.k_sf * 11) % 7] if (!map[nn]) { nn = 'all' // 'all' if (!map[nn]) return } } } - note.map = map[nn] - if (note.map[1]) { // if print map - note.pit = note.map[1].pit - note.acc = note.map[1].acc - } - if (note.map[2]) // if color - note.color = note.map[2] - nn = note.map[3] - if (nn) // if play map - note.b40 = nn + 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 + note.midi = pit2mid(nn.pit + 19, nn.acc) } /* -- parse note or rest with pitch and length -- */ // 'line' is not always 'parse.line' function parse_basic_note(line, ulen) { @@ -1784,40 +1811,89 @@ if (e_is_note) s.sls[s.sls.length - 1].is_note = e_is_note return } } - syntax(1, "Lack of starting slur") + syntax(1, "End of slur without start") } + +// convert a diatonic pitch and accidental to a MIDI pitch with cents +function pit2mid(pit, acc) { + var p = [0, 2, 4, 5, 7, 9, 11][pit % 7], // chromatic pitch + o = ((pit / 7) | 0) * 12, // octave + p0, p1, s, b40 + + if (curvoice.snd_oct) + o += curvoice.snd_oct + if (acc == 3) // if natural accidental + acc = 0 + if (acc) { + if (typeof acc != "number") { + s = acc[0] / acc[1] // microtonal accidental + if (acc[1] == 100) // in cents + return p + o + s + } else { + s = acc // simple accidental + } + } else { + if (cfmt.temper) + return cfmt.temper[abc2svg.p_b40[pit % 7]] + o + return p + o + } + if (!cfmt.nedo) { // non equal temperament + if (!cfmt.temper) { + p += o + s // standard temperament + return p + } + } else { // equal temperament + if (typeof acc == "number") { // 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 + b40 = abc2svg.p_b40[pit % 7] + return cfmt.temper[b40] + o + s + } + } + + p0 = cfmt.temper[abc2svg.p_b40[pit % 7]] // main note + if (s > 0) { // sharp + p1 = cfmt.temper[(abc2svg.p_b40[pit % 7] + 1) % 40] + if (p1 < p0) + p1 += 12 + } else { // flat + p1 = cfmt.temper[(abc2svg.p_b40[pit % 7] + 39) % 40] + if (p1 > p0) + p1 -= 12 + s = -s + } + return p0 + o + (p1 - p0) * s +} // pit2mid() // (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, sl1 = [], line = parse.line, a_dcn_sav = a_dcn // save parsed decoration names - if (!grace - && curvoice.tie_s) { // if tie from previous note / grace note - tie_s = curvoice.tie_s - curvoice.tie_s = null - } - // handle the ties function do_ties(s, tie_s) { - var m, note + 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].b40 == note.b40) { + if (tie_s.notes[i].midi == mid) { tie_s.notes[i].tie_n = note note.s = s tie_s.tie_s = s break } @@ -1824,11 +1900,11 @@ } } else { for (s2 = tie_s.extra; s2; s2 = s2.next) { if (!s2.notes[0].tie_ty) continue - if (s2.notes[0].b40 == note.b40) { + 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 @@ -1853,15 +1929,19 @@ 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 (a_gch) - self.gch_build(s) + csan_add(s) if (parse.repeat_n) { s.repeat_n = parse.repeat_n; s.repeat_k = parse.repeat_k; parse.repeat_n = 0 } @@ -1888,10 +1968,12 @@ s.dur_orig = s.dur; s.notes = [{ pit: 18, dur: s.dur }] + } else { + glovar.mrest_p = true } break case 'y': s.type = C.SPACE; s.invis = true; @@ -1899,12 +1981,14 @@ c = line.next_char() if (c >= '0' && c <= '9') s.width = line.get_int() else s.width = 10 - if (tie_s) + if (tie_s) { curvoice.tie_s = tie_s + tie_s = null + } break case 'x': s.invis = true case 'z': s.type = C.REST; @@ -1926,12 +2010,10 @@ default: // accidental, chord, note if (curvoice.acc_tie) { acc_tie = curvoice.acc_tie curvoice.acc_tie = null } - if (curvoice.uscale) - s.uscale = curvoice.uscale; s.notes = [] // loop on the chord while (1) { @@ -1988,26 +2070,29 @@ 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 base-40 + // and keep the absolute pitch in (MIDI + cents) format i = note.acc 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 + i = curvoice.ckey.k_map[apit % 7] || 0 } - note.b40 = abc2svg.pab40(note.pit, i) + note.midi = pit2mid(apit, i) + if (curvoice.ckey.k_sndtran) + note.midi += abc2svg.b40m(curvoice.ckey.k_sndtran + + 122) - 36 if (curvoice.map && maps[curvoice.map]) - set_map(note) + set_map(note, i) // starting slurs if (sl1.length) { while (1) { i = sl1.shift() @@ -2101,10 +2186,14 @@ s.dur = s.notes[0].dur * curvoice.dur_fact } 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) { @@ -2221,11 +2310,14 @@ } if (a_dcn_sav) deco_cnv(a_dcn_sav, s, s.prev) if (parse.ottava.length) { - s.ottava = parse.ottava + if (grace) + grace.ottava = parse.ottava + else + s.ottava = parse.ottava parse.ottava = [] } if (parse.stemless) s.stemless = true s.iend = parse.bol + line.index @@ -2234,23 +2326,22 @@ // adjust the duration of the elements in a tuplet function tp_adj(s, fact) { var tim = s.time - curvoice.time = s.time + (curvoice.time - s.time) * fact + curvoice.time = tim + (curvoice.time - tim) * fact while (1) { //fixme: tuplets in grace notes? s.in_tuplet = true if (!s.grace) { s.time = tim if (s.dur) { - s.dur = Math.round(s.dur * fact) + s.dur *= fact tim += s.dur } } if (!s.next) { - s.dur = curvoice.time - s.time if (s.tpe) s.tpe++ else s.tpe = 1 break @@ -2428,10 +2519,23 @@ while (1) { c = line.char() if (!c) break + + // skip definitions if the current voice is ignored + if (curvoice.ignore) { + while (1) { + if (c == '[' + && line.buffer[line.index + 1] == 'V' + && line.buffer[line.index + 2] == ':') + break // [V:nn] found + c = line.next_char() + if (!c) + return + } + } // special case for '.' (dot) if (c == '.') { switch (line.buffer[line.index + 1]) { case '(': @@ -2486,13 +2590,12 @@ break case '\n': // line break if (cfmt.barsperstaff) break if (par_sy.voices[curvoice.v] - && par_sy.voices[curvoice.v].range == 0 - && curvoice.last_sym) - curvoice.last_sym.eoln = true + && par_sy.voices[curvoice.v].range == 0) + curvoice.eoln = true break case '&': // voice overlay if (grace) { syntax(1, errs.bad_grace) break @@ -2557,12 +2660,10 @@ } line.index--; sls.push(parse_vpos()) continue case ')': // slur end - if (curvoice.ignore) - break s = curvoice.last_sym if (s) { switch (s.type) { case C.SPACE: if (!s.notes) { @@ -2751,14 +2852,11 @@ case '|': if (grace) { syntax(1, errs.bar_grace) break } - c = line.buffer[line.index - 1]; - new_bar() - if (c == '.') - curvoice.last_sym.bar_dotted = true + new_bar(line.buffer[line.index - 1] == '.') continue case '}': s = curvoice.last_note if (!grace || !s) { syntax(1, errs.bad_char, c) @@ -2801,20 +2899,24 @@ goto_tune() } if (parse.tp) { tp = parse.tp + tpn = parse.tpn tps = parse.tps parse.tp = null } parse_seq() if (tp.length) { parse.tp = tp parse.tps = tps + parse.tpn = tpn } + if (sls.length) + syntax(1, "Start of slur without note") if (grace) { syntax(1, "No end of grace note sequence"); curvoice.last_sym = grace.prev; curvoice.last_note = last_note_sav if (grace.prev) @@ -2824,9 +2926,8 @@ 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 - && curvoice.last_sym) - curvoice.last_sym.eoln = true + && par_sy.voices[curvoice.v].range == 0) + curvoice.eoln = true } Index: core/subs.js ================================================================== --- core/subs.js +++ core/subs.js @@ -1,8 +1,8 @@ // abc2svg - subs.js - text output // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 Jean-Francois Moine // // This file is part of abc2svg-core. // // abc2svg-core is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by @@ -15,10 +15,37 @@ // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with abc2svg-core. If not, see . +// add font styles + var sheet +var add_fstyle = typeof document != "undefined" ? + function(s) { + var e + if (!sheet) { + if (abc2svg.sheet) { // if styles from a previous generation + sheet = abc2svg.sheet + e = sheet.cssRules.length + while (--e >= 0) + sheet.deleteRule(e) + } else { + e = document.createElement('style') + document.head.appendChild(e) + abc2svg.sheet = sheet = e.sheet + } + } + s = s.match(/[^{]+{[^}]+}/g) // insert each style + while (1) { + e = s.shift() + if (!e) + break + sheet.insertRule(e, sheet.cssRules.length) + } + } // add_fstyle() + : function(s) { font_style += "\n" + s } + // width of characters according to the font type // these tables were created from the font 'Liberation' // serif var @@ -78,26 +105,37 @@ // estimate the width and height of a string .. var strwh = typeof document != "undefined" ? // .. by the browser - (function(el) { - el.style.position = 'absolute'; - el.style.top = '-1000px'; - el.style.padding = '0'; + (function() { + // (re)create a text element + var el = document.createElement('text') + el.style.position = 'absolute' + el.style.top = '-1000px' + el.style.padding = '0' document.body.appendChild(el) return function(str) { + if (str.wh) + return str.wh var c, font = gene.curfont, h = font.size, w = 0, n = str.length, i0 = 0, i = 0 - el.style.font = style_font(font).slice(5); + el.className = font_class(font) + el.style.lineHeight = 1 + + if (typeof str == "object") { // if string already converted + el.innerHTML = str + str.wh = [ el.clientWidth, el.clientHeight ] + return str.wh + } str = str.replace(/<|>|&[^&]*?;|&| /g, function(c){ switch (c) { case '<': return "<" case '>': return ">" @@ -120,10 +158,11 @@ i++ continue } el.innerHTML = str.slice(i0, i); w += el.clientWidth +//fixme: bad result if space(s) at end of string if (font.size > h) h = font.size; el.style.font = style_font(font).slice(5); i += 2; @@ -133,11 +172,11 @@ w += el.clientWidth; gene.curfont = font return [w, h] } - })(document.createElement('div')) : + })() : // .. by internal tables function(str) { var font = gene.curfont, swfac = font.swfac, @@ -176,32 +215,43 @@ } gene.curfont = font return [w, h] } -// set the default and current font -function set_font(xxx) { - if (typeof xxx == "string") - xxx = get_font(xxx); - cw_tb = xxx.name.slice(0, 4) == 'sans' ? ssw_tb : sw_tb; - gene.curfont = gene.deffont = xxx -} - -// output a string handling the font changes -function out_str(str) { - var n_font, - o_font = gene.curfont, - c_font = o_font; - - output += str.replace(/<|>|&[^&]*?;|&| |\$./g, function(c){ +// convert a string to a SVG text, handling the font changes +// The string size is memorized into the String. +function str2svg(str) { + // check if the string is already converted + if (typeof str == "object") + return str + + var o, n_font, wh, + o_font = gene.curfont, + c_font = o_font + + // start a '' element + function tspan(nf, of) { + var cl + + if (nf.class + && nf.name == of.name + && nf.size == of.size + && nf.weight == of.weight + && nf.style == of.style) + cl = nf.class // change only the class + else + cl = font_class(nf) + + return '' + } // tspan() + + o = str.replace(/<|>|&[^&]*?;|&| |\$./g, function(c){ switch (c) { case '<': return "<" case '>': return ">" - case '&': - return "&" - case ' ': - return '  ' // space + nbspace + case '&': return "&" + case ' ': return '  ' // space + nbspace default: if (c[0] != '$') break if (c[1] == '0') n_font = gene.deffont @@ -211,37 +261,58 @@ break c = '' if (n_font == c_font) return c if (c_font != o_font) - c = ""; + c = "" c_font = n_font if (c_font == o_font) return c - return c + '' + return c + tspan(c_font, o_font) } return c // &xxx; }) if (c_font != o_font) { - output += ""; + 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 + return o +} // str2svg() + +// set the default and current font +function set_font(xxx) { + if (typeof xxx == "string") + xxx = get_font(xxx) + cw_tb = xxx.name.slice(0, 4) == 'sans' ? ssw_tb : sw_tb + gene.curfont = gene.deffont = xxx +} + +// output a string handling the font changes +function out_str(str) { + output += str2svg(str) } // output a string, handling the font changes // the action is: // 'c' align center // 'r' align right // 'j' justify - w is the line width // otherwise align left -function xy_str(x, y, str, +function xy_str(x, y, + str, // string or object String with attribute 'wh' action, // default: align left w, // needed for justify wh) { // optional [width, height] if (!wh) - wh = strwh(str); + wh = str.wh || strwh(str) output += '' break } out_str(str); output += "\n" - - if (!w && gene.curfont.box) { -// not in the SVG documentation, -// but this works for almost all browsers but firefox -// output += '\n'; -// // xy_str(x, y, str, action, w); -// output += '\n' - output += '\n' - } -} - -/* -- move trailing "The" to front, set to uppercase letters or add xref -- */ +} + +// move last capitalized word to front when after a comma function trim_title(title, is_subtitle) { var i if (cfmt.titletrim) { i = title.lastIndexOf(", ") @@ -296,16 +353,16 @@ i = 0 } else { if (i < title.length - cfmt.titletrim - 2) i = 0 } + if (i) + title = title.slice(i + 2).trim() + ' ' + title.slice(0, i) } if (!is_subtitle && cfmt.writefields.indexOf('X') >= 0) title = info.X + '. ' + title - if (i) - title = title.slice(i + 2).trim() + ' ' + title.slice(0, i) if (cfmt.titlecaps) return title.toUpperCase() return title } @@ -329,16 +386,18 @@ h = cfmt.subtitlespace } else { set_font("title"); h = cfmt.titlespace } - wh = strwh(title); - vskip(wh[1] + h) + wh = strwh(title) + wh[1] += gene.curfont.pad * 2 + vskip(wh[1] + h + gene.curfont.pad) + h = gene.curfont.pad if (cfmt.titleleft) - xy_str(0, 0, title, null, null, wh) + xy_str(0, h, title, null, null, wh) else - xy_str(get_lwidth() / 2, 0, title, "c", null, wh) + xy_str(get_lwidth() / 2, h, title, "c", null, wh) } /* -- output a header format '111 (222)' -- */ function put_inf2r(x, y, str1, str2, action) { if (!str1) { @@ -355,67 +414,57 @@ /* -- write a text block (%%begintext / %%text / %%center) -- */ function write_text(text, action) { if (action == 's') return // skip - set_font("text"); set_page(); - var wh, font, + + var wh, font, o, strlw = get_lwidth(), sz = gene.curfont.size, lineskip = sz * cfmt.lineskipfac, parskip = sz * cfmt.parskipfac, - p_start = block.started ? function(){} : blk_out, - p_flush = block.started ? svg_flush : blk_flush, i, j, x, words, w, k, ww, str; - p_start() switch (action) { default: // case 'c': // case 'r': + font = gene.curfont switch (action) { case 'c': x = strlw / 2; break - case 'r': x = strlw; break - default: x = 0; break + case 'r': x = strlw - font.pad; break + default: x = font.pad; break } j = 0 - font = gene.curfont while (1) { i = text.indexOf('\n', j) if (i < 0) { - str = text.slice(j); - wh = strwh(str); - gene.curfont = font; - vskip(wh[1] * cfmt.lineskipfac); - xy_str(x, 0, str, action, null, wh); - font = gene.curfont + 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); - p_flush(); + blk_flush() use_font(gene.curfont) while (text[i + 1] == '\n') { vskip(lineskip); i++ } if (i == text.length) break - p_start() } else { - str = text.slice(j, i); - wh = strwh(str); - gene.curfont = font; - vskip(wh[1] * cfmt.lineskipfac); - xy_str(x, 0, str, action, null, wh); - font = gene.curfont + str = str2svg(text.slice(j, i)) + vskip(str.wh[1] * cfmt.lineskipfac + font.pad * 2) + xy_str(x, font.pad, str, action) } j = i + 1 } vskip(parskip); - p_flush() + blk_flush() break case 'f': case 'j': j = 0 while (1) { @@ -426,56 +475,51 @@ words = text.slice(j, i); words = words.split(/\s+/); w = k = 0 font = gene.curfont for (j = 0; j < words.length; j++) { - ww = strwh(words[j] + ' ')[0]; + ww = strwh(words[j])[0]; w += ww if (w >= strlw) { - str = words.slice(k, j).join(' '); - gene.curfont = font; - wh = strwh(str); - gene.curfont = font; - vskip(wh[1] * cfmt.lineskipfac); - xy_str(0, 0, str, action, strlw, wh); - font = gene.curfont; + str = str2svg(words.slice(k, j).join(' ')) + vskip(str.wh[1] * cfmt.lineskipfac) + xy_str(0, 0, str, action, strlw) k = j; w = ww } - } - if (w != 0) { - str = words.slice(k).join(' '); - gene.curfont = font; - wh = strwh(str); - gene.curfont = font; - vskip(wh[1] * cfmt.lineskipfac); - xy_str(0, 0, str, null, null, wh) + w += cwidf(' ') + } + if (w != 0) { // last line + str = str2svg(words.slice(k).join(' ')) + vskip(str.wh[1] * cfmt.lineskipfac) + xy_str(0, 0, str) } vskip(parskip); - p_flush() + blk_flush() if (i < 0) break while (text[i + 2] == '\n') { vskip(lineskip); i++ } if (i == text.length) break - p_start(); use_font(gene.curfont); j = i + 2 } break } } /* -- output the words after tune -- */ function put_words(words) { - var p, i, j, n, nw, i2, i_end, have_text; + var p, i, j, nw, w, lw, x1, x2, i1, i2, do_flush, + 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, right) { + function put_wline(p, x) { var i = 0, j, k if (p[i] == '$' && p[i + 1] >= '0' && p[i + 1] <= '9') i += 2; k = 0; @@ -495,84 +539,95 @@ if (k != 0) xy_str(x, 0, p.slice(j, k), 'r') if (i < p.length) xy_str(x + 5, 0, p.slice(i), 'l') - return i >= p.length && k == 0 } // put_wline() set_font("words") + vskip(cfmt.wordsspace) + svg_flush() - /* see if we may have 2 columns */ - var middle = get_lwidth() / 2, - max2col = (middle - 45.) / cwidf('a'); - n = 0; - words = words.split('\n'); + // estimate the width of the lines + words = words.split('\n') nw = words.length for (i = 0; i < nw; i++) { p = words[i] -/*fixme:utf8*/ - if (p.length > max2col) { - n = 0 - break - } - if (!p) { - if (have_text) { - n++; - have_text = false - } - } else { - have_text = true - } - } - if (n > 0) { - i = n = ((n + 1) / 2) | 0; - have_text = false - for (i_end = 0; i_end < nw; i_end++) { - p = words[i_end]; - j = 0 - while (p[j] == ' ') - j++ - if (j == p.length) { - if (have_text && --i <= 0) - break - have_text = false - } else { - have_text = true - } - } - i2 = i_end + 1 - } else { - i2 = i_end = nw - } - - /* output the text */ - vskip(cfmt.wordsspace) - - for (i = 0; i < i_end || i2 < nw; i++) { -//fixme:should also permit page break on stanza start - if (i < i_end && words[i].length == 0) { - blk_out(); - use_font(gene.curfont) - } - vskip(cfmt.lineskipfac * gene.curfont.size) - if (i < i_end) - put_wline(words[i], 45., 0) - if (i2 < nw) { - if (put_wline(words[i2], 20. + middle, 1)) { - if (--n == 0) { - if (i < i_end) { - n++ - } else if (i2 < words.length - 1) { - - /* center the last words */ -/*fixme: should compute the width average.. */ - middle *= .6 - } - } - } - i2++ + if (!p) { + while (i + 1 < nw && !words[i + 1]) + i++ + n++ + } else if (p.length > maxn) { + maxn = p.length + i1 = i // keep this line + } + } + + w = get_lwidth() / 2 // half line width + lw = strwh(words[i1])[0] + i1 = i2 = 0 + if (lw < w) { // if 2 columns + j = n >> 1 + for (i = 0; i < nw; i++) { + p = words[i] + if (!p) { + if (--j <= 0) + i1 = i + while (i + 1 < nw && !words[i + 1]) + i++ + if (j <= 0) { + i2 = i + 1 + break + } + } + } + n >>= 1 + } + if (i2) { + x1 = (w - lw) / 2 + 10 + x2 = x1 + w + } else { // one column + x2 = w - lw / 2 + 10 + } + + do_flush = true + for (i = 0; i < i1 || i2 < nw; i++, i2++) { + vskip(cfmt.lineskipfac * gene.curfont.size) + if (i < i1) { + p = words[i] + if (p) + put_wline(p, x1) + else + use_font(gene.curfont) + } + if (i2 < nw) { + p = words[i2] + if (p) { + put_wline(p, x2) + } else { + + + if (--n == 0) { + if (i < i1) { + n++ + } else if (i2 < nw - 1) { + + // center the last verse + x2 = w - lw / 2 + 10 + svg_flush() + } + } + } + } + + if (!words[i + 1] && !words[i2 + 1]) { + if (do_flush) { + svg_flush() + do_flush = false + } + } else { + do_flush = true } } } /* -- output history -- */ @@ -606,11 +661,10 @@ for (j = 1; j < str.length; j++) { vskip(h); xy_str(w, 0, str[j]) } vskip(h * .3); - blk_out(); use_font(gene.curfont) } } /* -- write heading with format -- */ @@ -643,55 +697,61 @@ i = 0 while (1) { while (p[i] == ' ') i++ - if (i >= p.length) - break c = p[i++] + if (!c) + break if (c < 'A' || c > 'Z') { - if (c == '+') { - if (fmt.length == 0 - || fmt.slice(-1) == '+') - continue - fmt = fmt.slice(0, -1) + '+' - } else if (c == ',') { - if (fmt.slice(-1) == '+') - fmt = fmt.slice(0, -1) + 'l' + switch (c) { + case '+': + align = '+' + c = p[i++] + break + case ',': fmt += '\n' + // fall thru + default: + continue + case '<': + align = 'l' + c = p[i++] + break + case '>': + align = 'r' + c = p[i++] + break } - continue + } else { + switch (p[i]) { // old syntax + case '-': + align = 'l' + i++ + break + case '1': + align = 'r' + i++ + break + case '0': + i++ + // fall thru + default: + align = 'c' + break + } } if (!info_val[c]) { if (!info[c]) continue info_val[c] = info[c].split('\n'); info_nb[c] = 1 } else { info_nb[c]++ } - fmt += c - switch (p[i]) { - case '-': - fmt += 'l' - i++ - break - case '0': - fmt += 'c' - i++ - break - case '1': - fmt += 'r' - i++ - break - default: - fmt += 'c' - break - } - } - if (fmt.slice(-1) == '+') - fmt = fmt.slice(0, -1) + 'l'; + fmt += align + c + } fmt += '\n' // loop on the blocks var ya = { l: cfmt.titlespace, @@ -711,18 +771,17 @@ // get the y offset of the top text yb.l = yb.c = yb.r = y = 0; j = i while (1) { - c = p[j++] - if (c == '\n') + align = p[j++] + if (align == '\n') break - align = p[j++] - if (align == '+') - align = p[j + 1] - else if (yb[align] != 0) + c = p[j++] + if (align == '+' || yb[align]) continue + str = info_val[c] if (!str) continue font_name = info_font[c] if (!font_name) @@ -737,22 +796,22 @@ } ya.l += y - yb.l; ya.c += y - yb.c; ya.r += y - yb.r while (1) { - c = p[i++] - if (c == '\n') + align = p[i++] + if (align == '\n') break - align = p[i++] - if (info_val[c].length == 0) + c = p[i++] + if (!info_val[c].length) continue str = info_val[c].shift() - if (align == '+') { + if (p[i] == '+') { info_nb[c]--; + i++ c = p[i++]; - align = p[i++] - if (info_val[c].length > 0) { + if (info_val[c].length) { if (str) str += ' ' + info_val[c].shift() else str = ' ' + info_val[c].shift() } @@ -768,11 +827,11 @@ x = xa[align]; y = ya[align] + sz if (c == 'Q') { /* special case for tempo */ self.set_width(glovar.tempo) - if (!glovar.tempo.del) { + if (!glovar.tempo.invis) { if (align != 'l') { tempo_build(glovar.tempo) w = glovar.tempo.tempo_wh[0] if (align == 'c') @@ -780,10 +839,13 @@ x -= w } writempo(glovar.tempo, x, -y) } } else if (str) { + if (c == 'T') + str = trim_title(str, + info_font.T[0] == 's') xy_str(x, -y, str, align) } if (c == 'T') { font_name = info_font.T = "subtitle"; @@ -808,11 +870,11 @@ } if (ya.c > ya.l) ya.l = ya.c if (ya.r > ya.l) ya.l = ya.r - if (i >= fmt.length) + if (i >= p.length) break ya.c = ya.r = ya.l } vskip(ya.l) } @@ -820,11 +882,10 @@ /* -- output the tune heading -- */ function write_heading() { var i, j, area, composer, origin, rhythm, down1, down2, lwidth = get_lwidth() - blk_out(); vskip(cfmt.topspace) if (cfmt.titleformat) { write_headform(lwidth); vskip(cfmt.musicspace) Index: core/svg.js ================================================================== --- core/svg.js +++ core/svg.js @@ -1,8 +1,8 @@ // abc2svg - svg.js - svg functions // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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,18 +17,17 @@ // You should have received a copy of the GNU Lesser General Public License // along with abc2svg-core. If not, see . var output = "", // output buffer style = '\ -\ntext, tspan{fill:currentColor}\ \n.stroke{stroke:currentColor;fill:none}\ \n.bW{stroke:currentColor;fill:none;stroke-width:1}\ \n.bthW{stroke:currentColor;fill:none;stroke-width:3}\ \n.slW{stroke:currentColor;fill:none;stroke-width:.7}\ \n.slthW{stroke:currentColor;fill:none;stroke-width:1.5}\ \n.sW{stroke:currentColor;fill:none;stroke-width:.7}\ -\n.mtx{font:130% music}', +\n.box{outline: 1px solid black;outline-offset: 1px}', font_style = '', posx = cfmt.leftmargin / cfmt.scale, // default x offset of the images posy = 0, // y offset in the block img = { // image width: cfmt.pagewidth, // width @@ -45,20 +44,20 @@ st: -1, v: -1, g: 0 // color: undefined }, - block = {} /* started & newpage */ + blkdiv = 0 // block of contiguous SVGs + // -1: block started + // 0: no block + // 1: start a block + // 2: start a new page // glyphs in music font var tgls = { "mtr ": {x:0, y:0, c:"\u0020"}, // space brace: {x:0, y:0, c:"\ue000"}, - hl: {x:-4, y:0, c:"\ue022"}, - hl1: {x:-6, y:0, c:"\ue023"}, -// hl2: {x:-6, y:0, c:"\ue023"}, // (unused) - ghl: {x:-4, y:0, c:"\ue024"}, lphr: {x:0, y:24, c:"\ue030"}, mphr: {x:0, y:24, c:"\ue038"}, sphr: {x:0, y:27, c:"\ue039"}, rdots: {x:-1, y:0, c:"\ue043"}, // repeat dots dsgn: {x:-4, y:-4, c:"\ue045"}, // D.S. @@ -101,11 +100,11 @@ "circle-x": {x:-3.7, y:0, c:"\ue0b3"}, // 'circle-x' note head srep: {x:-5, y:0, c:"\ue101"}, "dot+": {x:-5, y:0, sc:.7, c:"\ue101"}, diamond: {x:-4, y:0, c:"\ue1b9"}, triangle: {x:-4, y:0, c:"\ue1bb"}, - dot: {x:-2, y:0, c:"\ue1e7"}, + dot: {x:-1, y:0, c:"\ue1e7"}, flu1: {x:-.3, y:0, c:"\ue240"}, // flags fld1: {x:-.3, y:0, c:"\ue241"}, flu2: {x:-.3, y:0, c:"\ue242"}, fld2: {x:-.3, y:0, c:"\ue243"}, flu3: {x:-.3, y:3.5, c:"\ue244"}, @@ -112,19 +111,25 @@ fld3: {x:-.3, y:-4, c:"\ue245"}, flu4: {x:-.3, y:8, c:"\ue246"}, fld4: {x:-.3, y:-9, c:"\ue247"}, flu5: {x:-.3, y:12.5, c:"\ue248"}, fld5: {x:-.3, y:-14, c:"\ue249"}, - "acc-1": {x:-1, y:0, c:"\ue260"}, // flat - acc3: {x:-1, y:0, c:"\ue261"}, // natural - acc1: {x:-2, y:0, c:"\ue262"}, // sharp + "acc-1": {x:-1, y:0, c:"\ue260"}, // flat + "cacc-1": {x:-18, y:0, c:"\ue26a\ue260\ue26b"}, // courtesy flat (note deco) + "sacc-1": {x:-1, y:0, sc:.7, c:"\ue260"}, // small flat (editorial) + acc3: {x:-1, y:0, c:"\ue261"}, // natural + "cacc3": {x:-18, y:0, c:"\ue26a\ue261\ue26b"}, // courtesy natural (note deco) + sacc3: {x:-1, y:0, sc:.7, c:"\ue261"}, // small natural (editorial) + acc1: {x:-2, y:0, c:"\ue262"}, // sharp + "cacc1": {x:-18, y:0, c:"\ue26a\ue262\ue26b"}, // courtesy sharp (note deco) + sacc1: {x:-2, y:0, sc: .7, c:"\ue262"}, // small sharp (editorial) acc2: {x:-3, y:0, c:"\ue263"}, // double sharp "acc-2": {x:-3, y:0, c:"\ue264"}, // double flat - "acc-1_1_4": {x:-2, y:0, c:"\ue280"}, // quarter-tone flat - "acc-1_3_4": {x:-3, y:0, c:"\ue281"}, // three-quarter-tones flat - acc1_1_4: {x:-1, y:0, c:"\ue282"}, // quarter-tone sharp - acc1_3_4: {x:-3, y:0, c:"\ue283"}, // three-quarter-tones sharp + "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"}, 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"}, @@ -176,11 +181,11 @@ // "mtrc.": {x:0, y:0, c:"\ue914"}, // (unused) mtrc: {x:0, y:0, c:"\ue915"}, // tempus imperfectum // "mtrc|": {x:0, y:0, c:"\ue918"}, // (unused) "mtr.": {x:0, y:0, c:"\ue920"}, // prolatione perfecta "mtr|": {x:0, y:0, c:"\ue925"}, // (twice as fast) - longa: {x:-3.7, y:0, c:"\ue95d"}, + longa: {x:-4.7, y:0, c:"\ue95d"}, custos: {x:-4, y:3, c:"\uea02"}, ltr: {x:2, y:6, c:"\ueaa4"} // long trill element } // glyphs to put in @@ -261,11 +266,11 @@ if (ie < 0) break ie += 3 + tag.length } if (text.substr(is, 7) == '\n'; stv_g.g++ } function g_close() { + glout() stv_g.g--; output += '\n' } // external SVG string @@ -524,10 +556,58 @@ out_XYAB('\n' + gla[2] = "" + } + + // stems + if (!gla[3].length) + return + output += '\n' +} // glout() // output a glyph function xygl(x, y, gl) { // (avoid ps<->js loop) // if (psxygl(x, y, gl)) @@ -538,15 +618,19 @@ } else { var tgl = tgls[gl] if (tgl) { x += tgl.x * stv_g.scale; y -= tgl.y - if (tgl.sc) + if (tgl.sc) { out_XYAB('B\n', x, y, tgl.sc, tgl.c); - else - out_XYAB('A\n', x, y, tgl.c) + } else { +// out_XYAB('A\n', x, y, tgl.c) + gla[0].push(sx(x)) + gla[1].push(sy(y)) + gla[2] += tgl.c + } } else { error(1, null, 'no definition of $1', gl) } } } @@ -561,16 +645,10 @@ y -= 4 } out_XYAB('\n', x, y, dx, -dy) } -// tuplet value - the staves are not defined -function out_bnum(x, y, str) { - out_XYAB('A\n', - x, y, str.toString()) -} // staff system brace function out_brace(x, y, h) { //fixme: '-6' depends on the scale x += posx - 6; y = posy - y; @@ -617,12 +695,13 @@ if (h < 0) dx = -dx; // down x += dx * stv_g.scale if (stv_g.v >= 0) slen /= voice_tb[stv_g.v].scale; - out_XYAB('\n', // stem - x, y, slen) + gla[3].push(sx(x)) + gla[3].push(sy(y)) + gla[3].push(slen) if (!nflags) return y += h if (h > 0) { // up @@ -643,14 +722,11 @@ } } } } else { // straight output += '= 0) { out_XYAB('MX Yl7 -3.2 0 -3.2 -7 3.2z\n', x, y); y += 5.4 } @@ -723,14 +797,14 @@ // tuplet bracket with number - the staves are not defined function out_tubrn(x, y, dx, dy, up, str) { var sw = str.length * 10, h = up ? -3 : 3; - out_XYAB('A\n', - x + dx / 2, y + dy / 2, str); - dx /= stv_g.scale + set_font("tuplet") + xy_str(x + dx / 2, y + dy / 2 - gene.curfont.size * .5 + 2, + str, 'c') + dx /= stv_g.scale if (!up) y += 6; output += '' + + str.push('' + p + '') j = p.length > 1 ? 2 : 1 // (note and optional dot) w += j * gene.curfont.swfac dy = '' } @@ -1046,11 +1131,15 @@ if (s.tempo) { // with a number of beats per minute str.push(s.tempo) w += strwh(s.tempo.toString())[0] } else { // with a beat as a note p = tempo_note(s, s.new_beat) - str.push('' + + str.push('' + p + '') j = p.length > 1 ? 2 : 1 w += j * gene.curfont.swfac dy = 'y' } @@ -1098,11 +1187,11 @@ '" height="' + bh.toFixed(1) + '"/>\n' } // don't display anymore - s.del = true + s.invis = true } // writempo() // update the vertical offset function vskip(h) { posy += h @@ -1111,14 +1200,17 @@ // create the SVG image of the block function svg_flush() { if (multicol || !output || !user.img_out || posy == 0) return - var head = '\n' } else { - head += '\n\twidth="' + img.width.toFixed(0) + + head += ' width="' + img.width.toFixed(0) + 'px" height="' + posy.toFixed(0) + 'px">\n' } - if (style || font_style || musicfont) { - head += '\n' - } - defs += fulldefs + head += fulldefs + + if (style || font_style) + head += '\n' + if (defs) head += '' + defs + '\n\n' // if %%pagescale != 1, do a global scale // (with a container: transform scale in does not work @@ -1168,48 +1256,37 @@ } if (psvg) // if PostScript support psvg.ps_flush(true); // + setg(0) + // start a block if needed + if (blkdiv > 0) { + user.img_out(blkdiv == 1 ? + '
' : + '
') + blkdiv = -1 // block started + } user.img_out(head + output + g + ""); output = "" font_style = '' - if (cfmt.fullsvg) { + if (cfmt.fullsvg && typeof document == "undefined") { defined_glyph = {} - for (var i = 0; i < font_tb.length; i++) + for (i = 0; i < font_tb.length; i++) font_tb[i].used = false } else { - musicfont = ''; style = ''; fulldefs = '' } defs = ''; posy = 0 } -// output a part of a block of images -function blk_out() { - if (multicol || !user.img_out) - return - blk_flush() - if (user.page_format && !block.started) { - block.started = true - if (block.newpage) { - block.newpage = false; - user.img_out('
') - } else { - user.img_out('
') - } - } -} -Abc.prototype.blk_out = blk_out - -// output the end of a block (or tune) +// mark the end of a
block function blk_flush() { svg_flush() - if (block.started) { - block.started = false; + if (blkdiv < 0 && (!parse.state || cfmt.splittune)) { user.img_out('
') + blkdiv = 0 } } Abc.prototype.blk_flush = blk_flush Index: core/tail.js ================================================================== --- core/tail.js +++ core/tail.js @@ -1,8 +1,8 @@ // abc2svg - tail.js // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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 @@ -19,19 +19,21 @@ // initialize init_tune() // Abc functions used by the modules +Abc.prototype.a_de = function() { return a_de } Abc.prototype.add_style = function(s) { style += s }; Abc.prototype.cfmt = function() { return cfmt }; Abc.prototype.clone = clone; Abc.prototype.deco_cnv = deco_cnv; +Abc.prototype.defs_add = defs_add +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; -Abc.prototype.get_a_gch = function() { return a_gch }; Abc.prototype.get_bool = get_bool; Abc.prototype.get_cur_sy = function() { return cur_sy }; Abc.prototype.get_curvoice = function() { return curvoice }; Abc.prototype.get_delta_tb = function() { return delta_tb }; Abc.prototype.get_decos = function() { return decos }; @@ -51,10 +53,11 @@ Abc.prototype.get_staff_tb = function() { return staff_tb }; Abc.prototype.get_top_v = function() { return par_sy.top_voice }; Abc.prototype.get_tsfirst = function() { return tsfirst }; Abc.prototype.get_unit = get_unit; Abc.prototype.get_voice_tb = function() { return voice_tb }; +Abc.prototype.glout = glout Abc.prototype.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; @@ -65,10 +68,13 @@ Abc.prototype.psdeco = empty_function; Abc.prototype.psxygl = empty_function; Abc.prototype.set_cur_sy = function(sy) { cur_sy = sy }; Abc.prototype.set_dscale = set_dscale; Abc.prototype.set_font = set_font; +Abc.prototype.set_a_gch = function(s, a) { a_gch = a; csan_add(s) } +Abc.prototype.set_hl = set_hl +Abc.prototype.set_pagef = function() { blkdiv = 1 } Abc.prototype.set_scale = set_scale; Abc.prototype.set_tsfirst = function(s) { tsfirst = s }; Abc.prototype.set_v_param = set_v_param; Abc.prototype.strwh = strwh; Abc.prototype.stv_g = function() { return stv_g }; @@ -75,10 +81,11 @@ Abc.prototype.svg_flush = svg_flush; Abc.prototype.syntax = syntax; Abc.prototype.tunes = tunes Abc.prototype.unlksym = unlksym; Abc.prototype.use_font = use_font; +Abc.prototype.vskip = vskip Abc.prototype.xy_str = xy_str; Abc.prototype.xygl = xygl; var hook_init // set after setting the first module hooks Index: core/tune.js ================================================================== --- core/tune.js +++ core/tune.js @@ -1,8 +1,8 @@ // abc2svg - tune.js - tune generation // -// Copyright (C) 2014-2019 Jean-Francois Moine +// Copyright (C) 2014-2020 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 @@ -57,19 +57,17 @@ /* -- link a ABC symbol into the current voice -- */ function sym_link(s) { if (!s.fname) set_ref(s) - if (!curvoice.ignore) { 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 if (s.dur && !s.grace) @@ -77,10 +75,14 @@ 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 + } } /* -- add a new symbol in a voice -- */ function sym_add(p_voice, type) { var s = { @@ -102,45 +104,10 @@ s.iend = s2.iend } return s } -/* -- expand a multi-rest into single rests and measure bars -- */ -function mrest_expand(s) { - var p_voice, s2, next, - nb = s.nmes, - dur = s.dur / nb - - /* change the multi-rest (type bar) to a single rest */ - var a_dd = s.a_dd; - s.type = C.REST; - s.dur = s.dur_orig = dur; - - /* add the bar(s) and rest(s) */ - next = s.next; - p_voice = s.p_v; - p_voice.last_sym = s; - p_voice.time = s.time + dur; - p_voice.cst = s.st; - s2 = s - while (--nb > 0) { - s2 = sym_add(p_voice, C.BAR); - s2.bar_type = "|"; - s2 = sym_add(p_voice, C.REST); - if (s.invis) - s2.invis = true; - s2.dur = s2.dur_orig = dur; - p_voice.time += dur - } - s2.next = next - if (next) - next.prev = s2; - - /* copy the mrest decorations to the last rest */ - s2.a_dd = a_dd -} - /* -- sort all symbols by time and vertical sequence -- */ // weight of the symbols !! depends on the symbol type !! var w_tb = new Uint8Array([ 4, // bar 1, // clef @@ -166,11 +133,10 @@ var s, s2, p_voice, v, time, w, wmin, ir, multi, prev, nb, ir2, v2, fl, new_sy, nv = voice_tb.length, vtb = [], vn = [], // voice indexed by range - mrest_time = -1, sy = cur_sy for (v = 0; v < nv; v++) vtb.push(voice_tb[v].sym) @@ -224,55 +190,15 @@ time = s.time; wmin = w } else if (w < wmin) { wmin = w } - if (s.type == C.MREST) { - if (s.nmes == 1) - mrest_expand(s) - else if (multi > 0) - mrest_time = time - } } if (wmin > 127) break // done - /* if some multi-rest and many voices, expand */ - if (time == mrest_time) { - nb = 0 - for (ir = 0; ir < nv; ir++) { - 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.MREST) { - mrest_time = -1 /* some note or rest */ - break - } - if (nb == 0) { - nb = s.nmes - } else if (nb != s.nmes) { - mrest_time = -1 /* different duration */ - break - } - } - if (mrest_time < 0) { - for (ir = 0; ir < nv; ir++) { - v = vn[ir] - if (v == undefined) - break - s = vtb[v] - if (s && s.type == C.MREST) - mrest_expand(s) - } - } - } - /* link the vertical sequence */ for (ir = 0; ir < nv; ir++) { v = vn[ir] if (v == undefined) break @@ -355,33 +281,45 @@ } s.dur = s.time + s.dur - t; s.time = t } // end set_feathered_beam() - /* if Q: from tune header, put it at start of the music */ + // terminate voice cloning + 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.del) { - play problem + 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) { + 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.next = p_voice.sym + s.prev = p_voice.sym + s.next = p_voice.sym.next if (s.next) - s.next.prev = s; - p_voice.sym = s + 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 - && p_voice.sls.length) { // and no end of slur - while (1) { + if (!sys_chg) { // if not %%score + delete p_voice.eoln + while (1) { // set the end of slurs var sl = p_voice.sls.shift() if (!sl) break s = sl.note.s @@ -398,13 +336,11 @@ }) } else { syntax(1, "Lack of ending slur(s)") } } - } - if (p_voice.ignore) - p_voice.ignore = false + } // not %%score for (s = p_voice.sym; s; s = s.next) { if (s.time >= staves_found) break } for ( ; s; s = s.next) { @@ -430,83 +366,32 @@ 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) } } } -/* -- duplicate the voices as required -- */ -function dupl_voice() { - var p_voice, p_voice2, s, s2, g, g2, v, i, - nv = voice_tb.length - - for (v = 0; v < nv; v++) { - p_voice = voice_tb[v]; - p_voice2 = p_voice.clone - if (!p_voice2) - continue - p_voice.clone = null - for (s = p_voice.sym; s; s = s.next) { -//fixme: there may be other symbols before the %%staves at this same time - if (s.time >= staves_found) - break - } - p_voice2.clef = clone(p_voice.clef); - curvoice = p_voice2 - for ( ; s; s = s.next) { - if (s.type == C.STAVES) - continue - s2 = clone(s) - if (s.notes) { - s2.notes = [] - for (i = 0; i <= s.nhd; i++) - s2.notes.push(clone(s.notes[i])) - } - sym_link(s2) -// s2.time = s.time - if (p_voice2.second) - s2.second = true - else - delete s2.second - if (p_voice2.floating) - s2.floating = true - else - delete s2.floating - delete s2.a_ly; - g = s2.extra - if (!g) - continue - g2 = clone(g); - s2.extra = g2; - s2 = g2; - s2.v = p_voice2.v; - s2.p_v = p_voice2; - s2.st = p_voice2.st - for (g = g.next; g; g = g.next) { - g2 = clone(g) - if (g.notes) { - g2.notes = [] - for (i = 0; i <= g.nhd; i++) - g2.notes.push(clone(g.notes[i])) - } - s2.next = g2; - g2.prev = s2; - s2 = g2; - s2.v = p_voice2.v; - s2.p_v = p_voice2; - s2.st = p_voice2.st - } - } - } -} - /* -- create a new staff system -- */ function new_syst(init) { var st, v, sy_new = { voices: [], @@ -524,12 +409,11 @@ if (par_sy.voices[v]) { st = par_sy.voices[v].st var sy_staff = par_sy.staves[st], p_voice = voice_tb[v] - if (p_voice.staffnonote != undefined) - sy_staff.staffnonote = p_voice.staffnonote + sy_staff.staffnonote = p_voice.staffnonote if (p_voice.staffscale) sy_staff.staffscale = p_voice.staffscale; } } for (st = 0; st < par_sy.staves.length; st++) { @@ -541,137 +425,158 @@ } /* -- set the bar numbers -- */ // (possible hook) Abc.prototype.set_bar_num = function() { - var s, s2, tim, bar_time, bar_num, rep_dtime, - v = cur_sy.top_voice, - wmeasure = voice_tb[v].meter.wmeasure, - bar_rep = gene.nbar + var s, s2, tim, rep_tim, k, n, + bar_num = gene.nbar, + bar_tim = 0, // time of previous repeat variant + ptim = 0, // time of previous bar + wmeasure = voice_tb[cur_sy.top_voice].meter.wmeasure - /* don't count a bar at start of line */ + // don't count a bar at start of tune for (s = tsfirst; ; s = s.ts_next) { if (!s) return switch (s.type) { case C.METER: wmeasure = s.wmeasure + // fall thru case C.CLEF: case C.KEY: case C.STBRK: continue case C.BAR: - if (s.bar_num) { - gene.nbar = s.bar_num /* (%%setbarnb) */ - break - } - if (s.text // if repeat bar - && !cfmt.contbarnb) { - if (s.text[0] == '1') { - bar_rep = gene.nbar - } else { - gene.nbar = bar_rep; /* restart bar numbering */ - s.bar_num = gene.nbar - } - } + if (s.bar_num) + bar_num = s.bar_num // %%setbarnb) break } break } // at start of tune, check for an anacrusis - bar_time = s.time + wmeasure - if (s.time == 0) { - for (s2 = s.ts_next; s2; s2 = s2.ts_next) { - if (s2.type == C.BAR && s2.time) { - if (s2.time < bar_time) { // if anacrusis - s = s2; - bar_time = s.time + wmeasure - } - break - } + for (s2 = s.ts_next; s2; s2 = s2.ts_next) { + if (s2.type == C.BAR && s2.time) { + if (s2.time < wmeasure) { // if anacrusis + s = s2 + bar_tim = s.time + } + break } } // set the measure number on the top bars - bar_num = gene.nbar - for ( ; s; s = s.ts_next) { + if (!s.seqst) + continue switch (s.type) { case C.METER: + bar_num += (s.time - bar_tim) / wmeasure + bar_tim = s.time wmeasure = s.wmeasure - if (s.time < bar_time) - bar_time = s.time + wmeasure - break - case C.MREST: - bar_num += s.nmes - 1 - while (s.ts_next - && s.ts_next.type != C.BAR) - s = s.ts_next break case C.BAR: - if (s.time < bar_time) { // incomplete measure - if (s.text && s.text[0] == '1') { - bar_rep = bar_num; - rep_dtime = bar_time - s.time - } - break - } - - /* check if any repeat bar at this time */ - tim = s.time; - s2 = s - do { - if (s2.dur) - break - if (s2.type == C.BAR && s2.text) // if repeat bar - break - s2 = s2.next - } while (s2 && s2.time == tim); - - if (s.bar_num) - bar_num = s.bar_num // (%%setbarnb) - else - bar_num++ - - if (s2 && s2.type == C.BAR && s2.text) { - if (s2.text[0] == '1') { - rep_dtime = 0; - bar_rep = bar_num - } else { // restart bar numbering - if (!cfmt.contbarnb) - bar_num = bar_rep - if (rep_dtime) { // [1 inside measure - if (cfmt.contbarnb) - bar_num--; - bar_time = tim + rep_dtime - break - } - } - } - s.bar_num = bar_num; - bar_time = tim + wmeasure - - // skip the bars of the other voices - while (s.ts_next - && !s.ts_next.seqst) - s = s.ts_next - break - } - } - if (cfmt.measurenb < 0) /* if no display of measure bar */ - gene.nbar = bar_num /* update in case of more music to come */ -} + if (s.invis) + break + tim = s.time + if (s.bar_num) { + bar_num = s.bar_num // (%%setbarnb) + ptim = bar_tim = tim + continue + } + if (wmeasure == 1) { // if M:none + if (s.bar_dotted) + continue + if (s.text) { + if (!cfmt.contbarnb) { + if (s.text[0] == '1') + rep_tim = bar_num + else + bar_num = rep_tim + } + } + s.bar_num = ++bar_num + continue + } + + n = bar_num + (tim - bar_tim) / wmeasure + k = n - (n | 0) + if (cfmt.checkbars + && ((k && !s.bar_dotted && s.next) + || (tim > ptim + wmeasure && s.prev.type != C.MREST))) + error(0, s, "Bad measure duration") + if (tim > ptim + wmeasure) { // if more than one measure + n |= 0 + k = 0 + bar_tim = tim // re-synchronize + bar_num = n + } + if (s.text) { + if (s.text[0] == '1') { + if (cfmt.contbarnb) + rep_tim = bar_tim + k * wmeasure + else + rep_tim = tim + if (!k) + s.bar_num = n + } else { + if (cfmt.contbarnb) + bar_tim = rep_tim + else + bar_tim += tim - rep_tim + n = bar_num + (tim - bar_tim) / wmeasure + if (n == (n | 0)) + s.bar_num = n + } + } else { + if (k) + n -= k + s.bar_num = n + } + if (!k) + ptim = tim + break + } + } +} + +// convert a note to ABC +function not2abc(pit, acc) { + var i, + nn = '' + + if (acc && acc != 3) { + if (typeof acc == "number") { + nn = ['__', '_', '', '^', '^^'][acc + 2] + } else { + i = acc[0] + if (i > 0) { + nn += '^' + } else { + nn += '_' + i = -i + } + nn += i + '/' + acc[1] + } + } + nn += ntb[(pit + 75) % 7] + for (i = pit; i >= 21; i -= 7) + nn += "'" + for (i = pit; i < 14; i += 7) + nn += "," + return nn +} // not2abc() // note mapping // %%map map_name note [print [note_head]] [param]* function get_map(text) { if (!text) return - var i, note, notes, map, tmp, ns, ty, - a = info_split(text) + var i, note, notes, map, tmp, ns, + ty = '', + a = text.split(/\s+/) if (a.length < 3) { syntax(1, not_enough_p) return } @@ -681,26 +586,22 @@ } else { if (ns.indexOf("octave,") == 0 // remove the octave part || ns.indexOf("key,") == 0) { ty = ns[0] ns = ns.split(',')[1] - ns = ns.replace(/[,']*/, '').toUpperCase(); //' - } else { - ty = '' - } - if (ty == 'k') { - ns = 'k' + ntb.indexOf(ns) - } else { - tmp = new scanBuf; - tmp.buffer = ns - note = parse_acc_pit(tmp) - if (!note) { - syntax(1, "Bad note in %%map") - return - } - ns = ty + abc2svg.pab40(note.pit, note.acc).toString() - } + ns = ns.replace(/[,']+/, '').toUpperCase() //' + if (ns.indexOf("key,") == 0) + ns = ns.replace(/[=^_]+/, '') + } + tmp = new scanBuf + tmp.buffer = ns + note = parse_acc_pit(tmp) + if (!note) { + syntax(1, "Bad note in %%map") + return + } + ns = ty + not2abc(note.pit, note.acc) } notes = maps[a[0]] if (!notes) maps[a[0]] = notes = {} @@ -707,24 +608,27 @@ map = notes[ns] if (!map) notes[ns] = map = [] // try the optional 'print' and 'heads' parameters - if (!a[2]) + a.shift() + a.shift() + if (!a.length) return - i = 2 - if (a[2].indexOf('=') < 0) { - if (a[2][0] != '*') { + a = info_split(a.join(' ')) + i = 0 + if (a[0].indexOf('=') < 0) { + if (a[0][0] != '*') { tmp = new scanBuf; // print - tmp.buffer = a[2]; + tmp.buffer = a[0]; map[1] = parse_acc_pit(tmp) } - if (!a[3]) + if (!a[1]) return i++ - if (a[3].indexOf('=') < 0) { - map[0] = a[3].split(',') // heads + if (a[1].indexOf('=') < 0) { + map[0] = a[1].split(',') // heads i++ } } for (; i < a.length; i++) { @@ -746,11 +650,11 @@ tmp.buffer = a[i]; note = parse_acc_pit(tmp) if (a[i - 1][1] == 'r') map[1] = note else - map[3] = abc2svg.pab40(note.pit, note.acc) + map[3] = note break case "color=": if (!a[++i]) { syntax(1, not_enough_p) break @@ -766,11 +670,11 @@ var s, transp, sndtran if (curvoice.ckey.k_bagpipe || curvoice.ckey.k_drum) return - if (cfmt.transp && curvoice.transp) // if %%transpose and score= + if (cfmt.transp && curvoice.shift) // if %%transpose and shift= syntax(0, "Mix of old and new transposition syntaxes"); if (cfmt.transp != undefined || curvoice.transp != undefined @@ -796,11 +700,11 @@ while (1) { // set the transposition in the previous K: if (s.type == C.KEY) break s = s.prev if (!s) { - s = curvoice.key + s = curvoice.key // first key break } } } if (transp != undefined) @@ -850,49 +754,10 @@ if (ak) an = 3 // natural } else { return // same accidental (in the key) } - - d = note.micro_d - if (d /* microtone */ - && a != an) { /* different accidental type */ - n = note.micro_n -//fixme: double sharps/flats ?*/ -//fixme: does not work in all cases (tied notes, previous accidental) - switch (an) { - case 3: // natural - if (n > d / 2) { - n -= d / 2; - note.micro_n = n; - an = a - } else { - an = -a - } - break - case 2: // double sharp - if (n > d / 2) { - note.pit += 1; - n -= d / 2 - } else { - n += d / 2 - } - an = a - note.micro_n = n - break - case -2: // double flat - if (n >= d / 2) { - note.pit -= 1; - n -= d / 2 - } else { - n += d / 2 - } - an = a - note.micro_n = n - break - } - } note.acc = an } // adjust the pitches according to the transposition(s) function pit_adj() { @@ -949,11 +814,11 @@ sk = null } } } // pit_adj() -// get a transposition value as a base-40 interval +// get a abcm2ps/abcMIDI compatible transposition value as a base-40 interval // The value may be // - [+|-][s|f] // - [] % default is 'c' function get_transp(param) { if (param[0] == '0') @@ -960,20 +825,20 @@ return 0 if ("123456789-+".indexOf(param[0]) >= 0) { // by semi-tone var val = parseInt(param) if (isNaN(val) || val < -36 || val > 36) { //fixme: no source reference... - syntax(1, "Bad transpose value") + syntax(1, errs.bad_transp) return } val += 36 return (((val / 12) | 0) - 3) * 40 + (param.slice(-1) == 'b' ? abc2svg.ifb40 : abc2svg.isb40)[val % 12] } - return get_interval(param, true) + // return undefined } // get_transp() /* -- process a pseudo-comment (%% or I:) -- */ // (possible hook) Abc.prototype.do_pscom = function(text) { @@ -981,27 +846,43 @@ cmd = text.match(/(\w|-)+/) if (!cmd) return cmd = cmd[0]; + + // ignore the command if the voice is ignored, + // but not if %%score/%%staves! + if (curvoice && curvoice.ignore) { + switch (cmd) { + case "staves": + case "score": + break + default: + return + } + } + param = text.replace(cmd, '').trim() if (param.slice(-5) == ' lock') { fmt_lock[cmd] = true; 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) { - if (parse.state == 2) - goto_tune() s = new_block("text"); s.text = param s.opt = 'c' + s.font = cfmt.textfont return } write_text(param, 'c') return case "clef": @@ -1022,25 +903,33 @@ case "map": get_map(param) return case "maxsysstaffsep": if (parse.state == 3) { - par_sy.voices[curvoice.v].maxsep = get_unit(param) + val = get_unit(param) + if (isNaN(val)) { + syntax(1, errs.bad_val, "%%maxsysstaffsep") + return + } + par_sy.voices[curvoice.v].maxsep = val return } break case "multicol": - generate(1) + if (parse.state >= 2) { + if (parse.state == 2) + goto_tune() + curvoice = voice_tb[0] + s = new_block("mc_" + param) + break + } switch (param) { case "start": - blk_out(); multicol = { - posy: posy, - maxy: posy, + maxy: 0, lmarg: cfmt.leftmargin, - rmarg: cfmt.rightmargin, - state: parse.state + rmarg: cfmt.rightmargin } break case "new": if (!multicol) { syntax(1, "%%multicol new without start") @@ -1050,11 +939,11 @@ multicol.maxy = posy; cfmt.leftmargin = multicol.lmarg; cfmt.rightmargin = multicol.rmarg; img.chg = true; set_page(); - posy = multicol.posy + posy = 0 break case "end": if (!multicol) { syntax(1, "%%multicol end without start") break @@ -1071,13 +960,10 @@ default: syntax(1, "Unknown keyword '$1' in %%multicol", param) break } return - case "musicfont": - musicfont = param - return case "ottava": if (parse.state != 3) { if (parse.state != 2) return goto_tune() @@ -1141,26 +1027,29 @@ if (values[1]) { h2 = get_unit(values[1]) if (values[2]) len = get_unit(values[2]) } + if (isNaN(h1) || isNaN(h2) || isNaN(len)) { + syntax(1, errs.bad_val, "%%sep") + return + } } if (h1 < 1) h1 = 14 if (h2 < 1) h2 = h1 if (len < 1) len = 90 - if (parse.state == 3) { + if (parse.state >= 2) { s = new_block(cmd); s.x = (lwidth - len) / 2 / cfmt.scale; s.l = len / cfmt.scale; s.sk1 = h1; s.sk2 = h2 return } - blk_out(); vskip(h1); output += '\n'; vskip(h2); @@ -1208,18 +1097,23 @@ } s = { type: C.STBRK, dur:0 } - if (param[0] >= '0' && param[0] <= '9') { - s.xmx = get_unit(param) - if (param.slice(-1) == 'f') - s.stbrk_forced = true + if (param.slice(-1) == 'f') { + s.stbrk_forced = true + param = param.replace(/\sf$/, '') + } + if (param) { + val = get_unit(param) + if (isNaN(val)) { + syntax(1, errs.bad_val, "%%staffbreak") + return + } + s.xmx = val } else { s.xmx = 14 - if (param[0] == 'f') - s.stbrk_forced = true } sym_link(s) return case "stafflines": case "staffscale": @@ -1239,37 +1133,48 @@ } return case "sysstaffsep": //--fixme: may be global if (parse.state == 3) { - par_sy.voices[curvoice.v].sep = get_unit(param) + val = get_unit(param) + if (isNaN(val)) { + syntax(1, errs.bad_val, "%%sysstaffsep") + return + } + par_sy.voices[curvoice.v].sep = val return } break case "text": + set_font("text") if (parse.state >= 2) { - if (parse.state == 2) - goto_tune() s = new_block(cmd); s.text = param s.opt = cfmt.textoption + s.font = cfmt.textfont return } write_text(param, cfmt.textoption) 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) + return + } + } switch (parse.state) { case 0: cfmt.transp = 0 // fall thru case 1: case 2: - val = get_transp(param) - if (val) - cfmt.transp = (cfmt.transp || 0) + val + cfmt.transp = (cfmt.transp || 0) + val return } for (s = curvoice.last_sym; s; s = s.prev) { switch (s.type) { case C.NOTE: // insert a key @@ -1282,11 +1187,12 @@ default: continue } break } - do_info('V', curvoice.id + ' score=' + param) + curvoice.transp = val + set_transp() return case "tune": //fixme: to do return case "user": @@ -1300,15 +1206,19 @@ } curvoice.color = param return case "vskip": val = get_unit(param) + if (isNaN(val)) { + syntax(1, errs.bad_val, "%%vskip") + return + } if (val < 0) { syntax(1, "%%vskip cannot be negative") return } - if (parse.state == 3) { + if (parse.state >= 2) { s = new_block(cmd); s.sk = val return } vskip(val); @@ -1320,19 +1230,18 @@ case "pagewidth": case "printmargin": case "scale": case "staffwidth": if (parse.state >= 2) { - if (parse.state == 2) - goto_tune() s = new_block(cmd); s.param = param return } if (cmd == "newpage") { - blk_flush(); - block.newpage = true; + blk_flush() + if (user.page_format) + blkdiv = 2 // start the next SVG in a new page return } break } self.set_format(cmd, param) @@ -1348,31 +1257,32 @@ switch (type) { case "js": js_inject(text) break case "ml": - if (parse.state == 3) { + if (parse.state >= 2) { s = new_block(type); s.text = text } else { - svg_flush(); + blk_flush() if (user.img_out) user.img_out(text) } break case "svg": j = 0 while (1) { - i = text.indexOf('', i) if (j < 0) { syntax(1, "No in %%beginsvg sequence") break } - style += text.slice(i + 23, j).replace(/\s+$/, '') + style += text.slice(i + 1, j).replace(/\s+$/, '') } j = 0 while (1) { i = text.indexOf('\n', j) if (i < 0) @@ -1387,19 +1297,16 @@ break case "text": action = get_textopt(opt); if (!action) action = cfmt.textoption - if (text.indexOf('\\') >= 0) // convert the escape sequences - text = cnv_escape(text) - + set_font("text") if (parse.state >= 2) { - if (parse.state == 2) - goto_tune() s = new_block(type); s.text = text s.opt = action + s.font = cfmt.textfont break } write_text(text, action) break } @@ -1423,23 +1330,28 @@ } if (!voice_tb.length) return voice_adj(); - dupl_voice(); sort_all() /* define the time / vertical sequences */ if (!tsfirst) return self.set_bar_num() pit_adj() + if (info.P) + tsfirst.parts = info.P // for play + // give the parser result to the application if (user.get_abcmodel) - user.get_abcmodel(tsfirst, voice_tb, anno_type, info) + user.get_abcmodel(tsfirst, voice_tb, abc2svg.sym_name, info) if (user.img_out) // if SVG generation self.output_music() + + if (tsfirst) // if non void, keep tune data for upper layers + tunes.push([tsfirst, voice_tb, info, cfmt]) // if inside multicol, reset the parser if (!in_mc) return voice_tb = Object.create(voice_tb) @@ -1532,14 +1444,12 @@ a_vf = parse_staves(parm) // array of [vid, flags] if (!a_vf) return - if (voice_tb.length) { + if (voice_tb.length) voice_adj(true); - dupl_voice() - } /* create a new staff system */ var maxtime = 0, no_sym = true @@ -1592,34 +1502,24 @@ for (i = 0; i < a_vf.length; i++) { vid = a_vf[i][0]; p_voice = new_voice(vid); p_voice.time = maxtime; v = p_voice.v - if (i == 0) - par_sy.top_voice = p_voice.v - - // if the voice is already here, clone it - if (par_sy.voices[v]) { - p_voice2 = clone(p_voice); -// p_voice2.id += "_c" // for tests - par_sy.voices[voice_tb.length] = clone(par_sy.voices[v]); - v = voice_tb.length; - p_voice2.v = v; - p_voice2.sym = p_voice2.last_sym = null; - p_voice2.time = maxtime; - voice_tb.push(p_voice2) - delete p_voice2.clone - while (p_voice.clone) - p_voice = p_voice.clone; - p_voice.clone = p_voice2; - p_voice = p_voice2 - } else { + + 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++ + p_voice = p_voice.voice_down + if (!p_voice) + break + v = p_voice.v } - a_vf[i][0] = p_voice; - par_sy.voices[v].range = range++ } + par_sy.top_voice = a_vf[0][0].v /* change the behavior from %%staves to %%score */ if (cmd[1] == 't') { /* if %%staves */ for (i = 0; i < a_vf.length; i++) { flags = a_vf[i][1] @@ -1667,11 +1567,11 @@ p_voice.second = true } else { st++; if (!par_sy.staves[st]) { par_sy.staves[st] = { - stafflines: '|||||', + stafflines: p_voice.stafflines || "|||||", staffscale: 1 } } par_sy.staves[st].flags = 0 } @@ -1709,14 +1609,22 @@ par_sy.staves[st].flags ^= STOP_BAR } for (v = 0; v < voice_tb.length; v++) { p_voice = voice_tb[v] - if (!par_sy.voices[v]) { - p_voice.ignore = true + if (!par_sy.voices[v]) continue + + // set the staff of the overlay voices + p_voice2 = p_voice.voice_down + while (p_voice2) { + i = p_voice2.v + p_voice2.st = p_voice2.cst = + par_sy.voices[i].st = st + p_voice2 = p_voice2.voice_down } + par_sy.voices[v].second = p_voice.second; st = p_voice.st if (st > 0 && !p_voice.norepbra && !(par_sy.staves[st - 1].flags & STOP_BAR)) p_voice.norepbra = true @@ -1738,10 +1646,11 @@ p_voice.v = voice_tb.length; p_voice.id = id; p_voice.sym = p_voice.last_sym = null; p_voice.key = clone(curvoice.key) + p_voice.sls = [] delete p_voice.nm delete p_voice.snm delete p_voice.new_name delete p_voice.lyric_restart @@ -1748,10 +1657,11 @@ 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 voice_tb.push(p_voice) return p_voice } // clone_voice() @@ -1759,12 +1669,10 @@ function get_vover(type) { var p_voice2, p_voice3, range, s, time, v, v2, v3, line = parse.line /* treat the end of overlay */ - if (curvoice.ignore) - return if (type == '|' || type == ')') { if (!curvoice.last_note) { syntax(1, errs.nonote_vo) return @@ -1777,10 +1685,11 @@ if (curvoice.time != vover.p_voice.time) { syntax(1, "Wrong duration in voice overlay"); if (curvoice.time > vover.p_voice.time) vover.p_voice.time = curvoice.time } + curvoice.acc = [] // no accidental anymore curvoice = vover.p_voice; vover = null return } @@ -1813,33 +1722,21 @@ v2 = p_voice2.v; par_sy.voices[v2] = { st: curvoice.st, second: true } - var f_clone = curvoice.clone != undefined ? 1 : 0; range = par_sy.voices[curvoice.v].range for (v = 0; v < par_sy.voices.length; v++) { if (par_sy.voices[v] && par_sy.voices[v].range > range) - par_sy.voices[v].range += f_clone + 1 + par_sy.voices[v].range++ } par_sy.voices[v2].range = range + 1 - if (f_clone) { - p_voice3 = clone_voice(p_voice2.id + 'c'); - p_voice3.second = true; - v3 = p_voice3.v; - par_sy.voices[v3] = { - second: true, - range: range + 2 - } - p_voice2.clone = p_voice3 - } } p_voice2.ulen = curvoice.ulen p_voice2.dur_fact = curvoice.dur_fact - if (curvoice.uscale) - p_voice2.uscale = curvoice.uscale + p_voice2.acc = [] // no accidental if (!vover) { /* first '&' in a measure */ vover = { bar: true, p_voice: curvoice @@ -1876,12 +1773,12 @@ // 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.sym) +// return true // new voice (may appear in the middle of a tune) if (curvoice.time != 0) return false for (s = curvoice.last_sym; s; s = s.prev) if (w_tb[s.type] != 0) return false @@ -1888,20 +1785,35 @@ return true } // treat a clef found in the tune body function get_clef(s) { - var s2, s3 - if (is_voice_sig()) { curvoice.clef = s return } // clef change sym_link(s); s.clef_small = true + + // move the clef before a (not right repeat) bar + var s2 = s.prev + if (s2 && s2.type == C.BAR + && s2.bar_type[0] != ':') { + s.next = s2 + s.prev = s2.prev + if (s.prev) + s.prev.next = s + s2.prev = s + s2.next = null + curvoice.last_sym = s2 + if (s.soln) { + delete s.soln + curvoice.eoln = true + } + } } // treat K: (kp = key signature + parameters) function get_key(parm) { var v, p_voice, s, transp, sndtran, @@ -1934,11 +1846,11 @@ case 2: // K: at start of tune body goto_tune(true) break } if (a.length) - set_kv_parm(a); + set_kv_parm(a) if (!curvoice.ckey.k_bagpipe && !curvoice.ckey.k_drum && (cfmt.transp != undefined || curvoice.transp != undefined || curvoice.shift != undefined)) @@ -2028,15 +1940,16 @@ scale: 1, // st: 0, // cst: 0, ulen: glovar.ulen, dur_fact: 1, - key: clone(parse.ckey), // key at start of tune (parse) / line (gene) - ckey: clone(parse.ckey), // current key (parse) + key: clone(parse.ckey), // key at start of tune (parse / gene) + ckey: clone(parse.ckey), // current key (parse / gene) okey: clone(parse.ckey), // key without transposition (parse) meter: clone(glovar.meter), wmeasure: glovar.meter.wmeasure, + staffnonote: 1, clef: { type: C.CLEF, clef_auto: true, clef_type: "a", // auto time: 0 @@ -2065,38 +1978,35 @@ gene = {} a_de = [] // remove old decorations } // treat V: with many voices -function do_cloning(vs) { - var i, eol, - file = parse.file, - start = parse.eol + 1, // next line after V: - bol = start - - // search the end of the music to be cloned - while (1) { - eol = file.indexOf('\n', bol) - if (eol < 0) { - eol = 0 - break - } - - // stop on comment, or information field - if (/%.*|\n.*|.:.|\[.:/.test(file.slice(eol + 1, eol + 4))) - break - bol = eol + 1 - } +function do_cloning() { + var i, + clone = curvoice.clone, + vs = clone.vs, + a = clone.a, + bol = clone.bol, + eol = parse.istart, + parse_sav = parse, + file = parse.file + + delete curvoice.clone + + if (file[eol - 1] == '[') // if stop on [V:xx] + eol-- // insert the music sequence in each voice include++; - tosvg(parse.fname, file, start, eol) // first voice for (i = 0; i < vs.length; i++) { - get_voice(vs[i]); - tosvg(parse.fname, file, start, eol) + parse = Object.create(parse_sav) // create a new parse context + parse.line = Object.create(parse_sav.line) + get_voice(vs[i], a) + tosvg(parse.fname, file, bol, eol) } include-- + parse = parse_sav // restore the parse context } // treat a 'V:' info function get_voice(parm) { var v, vs, @@ -2104,28 +2014,51 @@ vid = a.shift() if (!vid) return // empty V: - if (vid.indexOf(',') > 0) { // if many voices - vs = vid.split(','); - vid = vs.shift() - } - - if (parse.state < 2) { - if (a.length) - memo_kv_parm(vid, a) - if (vid != '*' && parse.state == 1) - curvoice = new_voice(vid) + // if end of sequence with many voices, load the other voices + if (curvoice && curvoice.clone) + do_cloning() + + if (vid.indexOf(',') > 0) // if many voices + vs = vid.split(',') + else + vs = [vid] + + if (parse.state < 2) { // memorize the voice parameters + while (1) { + vid = vs.shift() + if (!vid) + break + if (a.length) + memo_kv_parm(vid, a) + if (vid != '*' && parse.state == 1) + curvoice = new_voice(vid) + } return } if (vid == '*') { syntax(1, "Cannot have V:* in tune body") return } - curvoice = new_voice(vid); + + curvoice = new_voice(vs[0]) + + // if many voice, memorize the start of sequence + if (vs.length > 1) { + vs.shift() + curvoice.clone = { + vs: vs, + a: a, + bol: parse.iend + } + 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(); @@ -2142,29 +2075,19 @@ par_sy.staves[nstaff] = { stafflines: curvoice.stafflines || "|||||", staffscale: 1 } } - - if (!par_sy.voices[v]) { -// if (cfmt.alignbars) -// syntax(1, "V: does not work with %%alignbars") - if (staves_found >= 0) - curvoice.ignore = true - } } if (!curvoice.filtered - && !curvoice.ignore + && par_sy.voices[v] && (parse.voice_opts || parse.tune_v_opts)) { curvoice.filtered = true; voice_filter() } - - if (vs) - do_cloning(vs) } // change state from 'tune header after K:' to 'in tune body' // curvoice is defined when called from get_voice() function goto_tune(is_K) { @@ -2197,11 +2120,11 @@ } else if (!curvoice) { curvoice = voice_tb[staves_found < 0 ? 0 : par_sy.top_voice] } if (!curvoice.init && !is_K) { - set_kv_parm([]); + set_kv_parm([]) set_transp() } // update some voice parameters for (v = 0; v < voice_tb.length; v++) { Index: editor/edit.css ================================================================== --- editor/edit.css +++ editor/edit.css @@ -1,18 +1,19 @@ /* edit.css - file used in the abc2svg editor */ -@page {margin-top: 1cm; margin-left: 0cm; margin-right: 0cm} +@page {margin:0} text tspan {white-space:pre; fill:currentColor} -svg {display:block} +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} + svg {margin-left:auto;margin-right:0} #dleft { width: auto; position: absolute; top: 0; bottom:0; left: 0; - background-color:#faf0e6; z-index: 2; + background-color:#faf0e6; overflow: auto; } #nav { position:relative; padding: 0; @@ -39,17 +40,18 @@ } .droplinks li:hover { outline: solid; outline-width: 2px } .droplinks li { padding: 0 5px 0 5px; margin: 2px } #dright { width: 80%; - max-width: 800px; + max-width: 840px; position: absolute; top: 0; bottom:0; right:0; - background-color: white; + background-color:#faf0e6; overflow: auto } #dright:hover {z-index: 10 !important} #dright:active {z-index: 10 !important} + textarea {background-color:#ffffff} .popup { position: absolute; top: 0; right: 20px; padding: 10px; background-color:#e0e0e0; border-radius: 5px; @@ -99,6 +101,5 @@ } #dright { margin:0; padding:0; border:0 } .newpage { page-break-before: always } .nobrk { page-break-inside: avoid } } -@page{margin:0} 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-2019 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 @@ -230,11 +230,10 @@ } } abc = new abc2svg.Abc(user); abc_images = ''; - abc.tosvg('edit', '%%bgcolor white'); // document.body.style.cursor = "wait"; syms = [] try { abc.tosvg(abc_fname[0], content) @@ -255,52 +254,57 @@ // show the 'Error' button if some error document.getElementById("er").style.display = elt_ref.diverr.innerHTML ? 'inline' : 'none'; } + +// get the source offset from the row and column numbers +function soffs(r, c) { + var m, + s = elt_ref.source, + o = 0 + while (--r >= 0) { + o = s.value.indexOf('\n', o) + 1 + if (o <= 0) + return s.value.length - 1 + } + m = s.value.indexOf('\n', o) // check if inside the line + o += c + if (o > m) + o = m + return o +} // soffs() // select a source ABC element on error function gotoabc(l, c) { - var s = elt_ref.source, - idx = 0; selsrc(0) - while (--l >= 0) { - idx = s.value.indexOf('\n', idx) + 1 - if (idx <= 0) { - alert(texts.bad_nb); - idx = s.value.length - 1; - c = 0 - break - } - } - c = Number(c) + idx; + c = soffs(l, Number(c)) s.focus(); - s.setSelectionRange(c, syms[c].iend || c + 1) + s.setSelectionRange(c, syms[c] ? syms[c].iend : c + 1) } // click in the target function selsvg(evt) { var v, - elt = evt.target, - cl = elt.getAttribute('class') + cl = evt.target.getAttribute('class') play.loop = false; evt.stopImmediatePropagation(); evt.preventDefault() // remove the context menu if active if (ctxMenu && ctxMenu.style.display == "block") { ctxMenu.style.display = "none" - return false + return } // stop playing if (play.playing && !play.stop) { play.stop = -1; play.abcplay.stop() - return false + return } // highlight the clicked element or clear the selection start s = elt_ref.source; if (cl && cl.substr(0, 4) == 'abcr') { @@ -498,12 +502,11 @@ // 2: Loop // 3: Continue function play_tune(what) { var i, si, ei, elt, C = abc2svg.C, - tunes = abc.tunes, - s = elt_ref.source.value + tunes = abc.tunes if (play.playing) { if (!play.stop) { play.stop = -1; play.abcplay.stop() @@ -624,11 +627,11 @@ // generate the play data of all tunes while (1) { elt = tunes.shift() if (!elt) break - play.abcplay.add(elt[0], elt[1]) + play.abcplay.add(elt[0], elt[1], elt[3]) } play.si = play.ei = null play.stop = 0 play.loop = false @@ -819,42 +822,56 @@ } set_pref() // set the preferences from local storage } // drag and drop -function drag_over(evt) { +function drag_enter(evt) { evt.stopImmediatePropagation(); evt.preventDefault() // allow drop +//fixme: "move" does not work +// evt.dataTransfer.dropEffect = evt.ctrlKey ? "move" : "copy" } -function dropped(evt) { +function drop(evt) { evt.stopImmediatePropagation(); evt.preventDefault() // check if text var data = evt.dataTransfer.getData("text") - if (data) { - evt.target.value = data; + if (data) { // insert the text at the mouse location + var x = evt.layerX, + y = elt_ref.source.scrollTop + evt.layerY, + h = elt_ref.source.offsetHeight / elt_ref.source.rows, + w = elt_ref.source.offsetWidth / elt_ref.source.cols + + w = (x / w) | 0 // column + h = (y / h) | 0 // row + h = soffs(h, w) + + var e = evt.target + e.value = e.value.slice(0, h) + + data + + e.value.slice(h) src_change() return } // check if file data = evt.dataTransfer.files // FileList object. - if (data.length != 0) { + if (data.length) { var reader = new FileReader(); reader.onload = function(evt) { elt_ref.source.value = evt.target.result; src_change() } reader.readAsText(data[0],"UTF-8") - return +// return } } -// render the music after 2 seconds on textarea change +// render the music 2 seconds after source change var timer function src_change() { clearTimeout(timer); if (!play.playing) timer = setTimeout(render, 2000) } // wait for scripts to be loaded -setTimeout(edit_init, 500) +window.addEventListener("load", edit_init) Index: editor/edit.xhtml ================================================================== --- editor/edit.xhtml +++ editor/edit.xhtml @@ -7,11 +7,11 @@ ABC edit with abc2svg - +
@@ -88,12 +88,11 @@

@@ -141,11 +140,12 @@