// abc2svg - abc2svg.js
//
// Copyright (C) 2014-2025 Jean-François 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// define the abc2svg object is not yet done
if (typeof abc2svg == "undefined")
	var abc2svg = {};

// constants
abc2svg.C = {
	BLEN: 1536,

	// symbol types
	BAR: 0,
	CLEF: 1,
	CUSTOS: 2,
	SM: 3,		// sequence marker (transient)
	GRACE: 4,
	KEY: 5,
	METER: 6,
	MREST: 7,
	NOTE: 8,
	PART: 9,
	REST: 10,
	SPACE: 11,
	STAVES: 12,
	STBRK: 13,
	TEMPO: 14,
	BLOCK: 16,
	REMARK: 17,

	// note heads
	FULL: 0,
	EMPTY: 1,
	OVAL: 2,
	OVALBARS: 3,
	SQUARE: 4,

	// position types
	SL_ABOVE: 0x01,		// position (3 bits)
	SL_BELOW: 0x02,
	SL_AUTO: 0x03,
	SL_HIDDEN: 0x04,
	SL_DOTTED: 0x08,	// modifiers
	SL_ALI_MSK: 0x70,	// align
		SL_ALIGN: 0x10,
		SL_CENTER: 0x20,
		SL_CLOSE: 0x40
    };

// !! tied to the symbol types in abc2svg.js !!
abc2svg.sym_name = ['bar', 'clef', 'custos', 'smark', '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
	new Int8Array([ 0,-1,-1, 0,-1,-1,-1 ]),	// 5 flat signs
	new Int8Array([ 0,-1,-1, 0, 0,-1,-1 ]),	// 4 flat signs
	new Int8Array([ 0, 0,-1, 0, 0,-1,-1 ]),	// 3 flat signs
	new Int8Array([ 0, 0,-1, 0, 0, 0,-1 ]),	// 2 flat signs
	new Int8Array([ 0, 0, 0, 0, 0, 0,-1 ]),	// 1 flat signs
	new Int8Array([ 0, 0, 0, 0, 0, 0, 0 ]),	// no accidental
	new Int8Array([ 0, 0, 0, 1, 0, 0, 0 ]),	// 1 sharp signs
	new Int8Array([ 1, 0, 0, 1, 0, 0, 0 ]),	// 2 sharp signs
	new Int8Array([ 1, 0, 0, 1, 1, 0, 0 ]),	// 3 sharp signs
	new Int8Array([ 1, 1, 0, 1, 1, 0, 0 ]),	// 4 sharp signs
	new Int8Array([ 1, 1, 0, 1, 1, 1, 0 ]),	// 5 sharp signs
	new Int8Array([ 1, 1, 1, 1, 1, 1, 0 ]),	// 6 sharp signs
	new Int8Array([ 1, 1, 1, 1, 1, 1, 1 ])	// 7 sharp signs
]

// base-40 representation of musical pitch
// (http://www.ccarh.org/publications/reprints/base40/)
abc2svg.p_b40 = new Int8Array(			// staff pitch to base-40
//		  C  D   E   F   G   A   B
		[ 2, 8, 14, 19, 25, 31, 37 ])
abc2svg.b40_p = new Int8Array(			// base-40 to staff pitch
//		       C		 D
		[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
//	      E		     F		       G
	2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
//	      A			B
	5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6 ])
abc2svg.b40_a = new Int8Array(			// base-40 to accidental
//		         C		      D
		[-2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3,
//		E		 F		      G
	-2, -1, 0, 1, 2, -2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2, -3,
//		A		     B
	-2, -1, 0, 1, 2, -3, -2, -1, 0, 1, 2 ])
abc2svg.b40_m = new Int8Array(			// base-40 to midi
//			 C		   D
		[-2, -1, 0, 1, 2, 0, 0, 1, 2, 3, 4, 0,
//	      E		     F		       G
	2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 0, 5, 6, 7, 8, 9, 0,
//	      A			    B
	7, 8, 9, 10, 11, 0, 9, 10, 11, 12, 13 ])
abc2svg.b40l5 = new Int8Array([			// base-40 to line of fifth
//		  C			  D
	-14, -7,  0,  7, 14,  0,-12, -5,  2,  9, 16,  0,
//		  E		      F			      G	
	-10, -3,  4, 11, 18,-15, -8, -1,  6, 13,  0,-13, -6,  1,  8, 15,  0,
//		  A			  B
	-11, -4,  3, 10, 17,  0, -9, -2,  5, 12, 19 ])

abc2svg.isb40 = new Int8Array(		// interval with sharp to base-40 interval
	[0, 1, 6,7,12,17,18,23,24,29,30,35])

abc2svg.pab40 = function(p, a) {
	p += 19				// staff pitch from C-1
   var	b40 = ((p / 7) | 0) * 40 + abc2svg.p_b40[p % 7]
	if (a && a != 3)		// if some accidental, but not natural
		b40 += a
	return b40
} // pit2b40()
abc2svg.b40p = function(b) {
	return ((b / 40) | 0) * 7 + abc2svg.b40_p[b % 40] - 19
} // b40p()
abc2svg.b40a = function(b) {
	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

// global fonts
abc2svg.font_tb = []	// fonts - index = font.fid
abc2svg.font_st = {}	// font style => font_tb index for incomplete user fonts

// cache for converting a duration into [head, dots, nflags]
abc2svg.hdn = {}

// font weight
// reference:
//	https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight
abc2svg.ft_w = {
	thin: 100,
	extralight: 200,
	light: 300,
	regular: 400,
	medium:  500,
	semi: 600,
	demi: 600,
	semibold: 600,
	demibold: 600,
	bold: 700,
	extrabold: 800,
	ultrabold: 800,
	black: 900,
	heavy: 900
}
abc2svg.ft_re = new RegExp('\
-?Thin|-?Extra Light|-?Light|-?Regular|-?Medium|\
-?[DS]emi|-?[DS]emi[ -]?Bold|\
-?Bold|-?Extra[ -]?Bold|-?Ultra[ -]?Bold|-?Black|-?Heavy/',
	"i")

// lyric prefix
abc2svg.lypre = /^\d.+\.|^[\d-]+\.?|^\w+:|^\(|^\)/;

// 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 }

// start of the Abc object
abc2svg.Abc = function(user) {
	"use strict";

    // constants
    var	C = abc2svg.C;

	// mask some unsafe functions
    var	require = empty_function,
	system = empty_function,
	write = empty_function,
	XMLHttpRequest = empty_function,
	std = null,
	os = null

// -- constants --

// staff system
var	OPEN_BRACE = 0x01,
	CLOSE_BRACE = 0x02,
	OPEN_BRACKET = 0x04,
	CLOSE_BRACKET = 0x08,
	OPEN_PARENTH = 0x10,
	CLOSE_PARENTH = 0x20,
	STOP_BAR = 0x40,
	FL_VOICE = 0x80,
	OPEN_BRACE2 = 0x0100,
	CLOSE_BRACE2 = 0x0200,
	OPEN_BRACKET2 = 0x0400,
	CLOSE_BRACKET2 = 0x0800,
	MASTER_VOICE = 0x1000,

	IN = 96,		// resolution 96 PPI
	CM = 37.8,		// 1 inch = 2.54 centimeter
	YSTEP			// number of steps for y offsets

// 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",
	must_note_rest: "!$1! must be on a note or a rest",
	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",
	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: '%',
		state: 0,
		ottava: [],
		line: new scanBuf
	},
	tunes = [],		// first time symbol and voice array per tune for playing
	psvg			// PostScript

// utilities
function clone(obj, lvl) {
	if (!obj)
		return obj
	var tmp = new obj.constructor
	for (var k in obj)
	    if (obj.hasOwnProperty(k)) {
		if (lvl && typeof obj[k] == "object")
			tmp[k] = clone(obj[k], lvl - 1)
		else
			tmp[k] = obj[k]
	    }
	return tmp
}

function errbld(sev, txt, fn, idx) {
	var i, j, l, c, h

	if (user.errbld) {
		switch (sev) {
		case 0: sev = "warn"; break
		case 1: sev = "error"; break
		default: sev= "fatal"; break
		}
		user.errbld(sev, txt, fn, idx)
		return
	}
	if (idx != undefined && idx >= 0) {
		i = l = 0
		while (1) {
			j = parse.file.indexOf('\n', i)
			if (j < 0 || j > idx)
				break
			l++;
			i = j + 1
		}
		c = idx - i
	}
	h = ""
	if (fn) {
		h = fn
		if (l)
			h += ":" + (l + 1) + ":" + (c + 1);
		h += " "
	}
	switch (sev) {
	case 0: h += "Warning: "; break
	case 1: h += "Error: "; break
	default: h += "Internal bug: "; break
	}
	user.errmsg(h + txt, l, c)
}

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
	}
	if (arguments.length > 3)
		msg = msg.replace(/\$./g, function(a) {
			switch (a) {
			case '$1': return a1
			case '$2': return a2
			case '$3': return a3
			default  : return a4
			}
		})
	if (s && s.fname)
		errbld(sev, msg, s.fname, s.istart)
	else
		errbld(sev, msg)
}

// scanning functions
function scanBuf() {
//	this.buffer = buffer
	this.index = 0;

	scanBuf.prototype.char = function() {
		return this.buffer[this.index]
	}
	scanBuf.prototype.next_char = function() {
		return this.buffer[++this.index]
	}
	scanBuf.prototype.get_int = function() {
		var	val = 0,
			c = this.buffer[this.index]
		while (c >= '0' && c <= '9') {
			val = val * 10 + Number(c);
			c = this.next_char()
		}
		return val
	}
}

function syntax(sev, msg, a1, a2, a3, a4) {
    var	s = {
		fname: parse.fname,
		istart: parse.istart + parse.line.index
	}

	error(sev, s, msg, a1, a2, a3, a4)
}

// inject javascript code
function js_inject(js) {
	eval('"use strict";\n' + js)
}
// abc2svg - deco.js - decorations
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// 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
	cross			// cross voice decorations

// decorations - populate with standard decorations
var decos = {
	dot: "0 stc 6 .7 1",
	tenuto: "0 emb 6 4 3",
	slide: "1 sld 5,5 7 1",
	arpeggio: "2 arp 12 10 3",
	roll: "3 roll 5,4 5 6",
	lowermordent: "3 lmrd 6,5 4 6",
	uppermordent: "3 umrd 6,5 4 6",
	trill: "3 trl 14 5 8",
	upbow: "3 upb 12,2 3 7",
	downbow: "3 dnb 8,2 4 6",
	gmark: "3 grm 7 4 6",
	wedge: "0 wedge 8 1.5 1",		// (staccatissimo or spiccato)
	longphrase: "5 lphr 0 1 16",
	mediumphrase: "5 mphr 0 1 16",
	shortphrase: "5 sphr 0 1 16",
	turnx: "3 turnx 7,2.5 5 6",
	invertedturn: "3 turn 7,2 5 6",
//	"0": "3 fng 5,5 3 3 0",			// dynamic - see deco_def()
//	"1": "3 fng 5,5 3 3 1",
//	"2": "3 fng 5,5 3 3 2",
//	"3": "3 fng 5,5 3 3 3",
//	"4": "3 fng 5,5 3 3 4",
//	"5": "3 fng 5,5 3 3 5",
	plus: "3 dplus 8,2 2 4",
	"+": "3 dplus 8,2 2 4",
	">": "5 accent 3.5,3.5 4 4",
	accent: "5 accent 3.5,3.5 4 4",
	emphasis: "5 accent 3.5,3.5 4 4",
	marcato: "3 marcato 9 5 5",
	"^": "3 marcato 9 5 5",
	mordent: "3 lmrd 6,5 4 6",
	open: "3 opend 8 3 3",
	snap: "3 snap 10 3 3",
	thumb: "3 thumb 10 3 3",
	turn: "3 turn 7,2.5 5 6",
	"trill(": "5 ltr 8 0 0",
	"trill)": "5 ltr 8 0 0",
	"8va(": "5 8va 12 6 6",
	"8va)": "5 8va 12 6 6",
	"8vb(": "4 8vb 10,5 6 6",
	"8vb)": "4 8vb 10,5 6 6",
	"15ma(": "5 15ma 12 9 9",
	"15ma)": "5 15ma 12 9 9",
	"15mb(": "4 15mb 12 9 9",
	"15mb)": "4 15mb 12 9 9",
	breath: "5 brth 0 1 16",
	caesura: "5 caes 0 1 20",
	short: "5 short 0 1 16",
	tick: "5 tick 0 1 16",
	coda: "5 coda 22,5 10 10",
	dacapo: "5 dacs 16 20 20 Da Capo",
	dacoda: "5 dacs 16 20 20 Da Coda",
	"D.C.": "5 dcap 16,3 6 6",
	"D.S.": "5 dsgn 16,3 6 6",
	"D.C.alcoda": "5 dacs 16 32 32 D.C. al Coda",
	"D.S.alcoda": "5 dacs 16 32 32 D.S. al Coda",
	"D.C.alfine": "5 dacs 16 32 32 D.C. al Fine",
	"D.S.alfine": "5 dacs 16 32 32 D.S. al Fine",
	fermata: "5 hld 12 7.5 7.5",
	fine: "5 dacs 16 12 12 Fine",
	invertedfermata: "7 hld 12 8 8",
	segno: "5 sgno 22,2 5 5",
	f: "6 f 12,5 3 4",
	ff: "6 ff 12,5 8 5",
	fff: "6 fff 12,5 11 9",
	ffff: "6 ffff 12,5 15 12",
	mf: "6 mf 12,5 8 10",
	mp: "6 mp 12,5 9 10",
	p: "6 p 12,5 3 6",
	pp: "6 pp 12,5 8 9",
	ppp: "6 ppp 12,5 14 11",
	pppp: "6 pppp 12,5 14 17",
	pralltriller: "3 umrd 6,5 4 6",
	sfz: "6 sfz 12,5 9 9",
	ped: "6 ped 9 6 10",
	"ped-up": "6 pedoff 9 4 4",
	"ped(": "7 lped 14 1 1",
	"ped)": "7 lped 14 1 1",
	"crescendo(": "6 cresc 15,2 0 0",
	"crescendo)": "6 cresc 15,2 0 0",
	"<(": "6 cresc 15,2 0 0",
	"<)": "6 cresc 15,2 0 0",
	"diminuendo(": "6 dim 15,2 0 0",
	"diminuendo)": "6 dim 15,2 0 0",
	">(": "6 dim 15,2 0 0",
	">)": "6 dim 15,2 0 0",
	"-(": "8 gliss 0 0 0",
	"-)": "8 gliss 0 0 0",
	"~(": "8 glisq 0 0 0",
	"~)": "8 glisq 0 0 0",
// internal
//	color: "10 0 0 0 0",
	invisible: "32 0 0 0 0",
	beamon: "33 0 0 0 0",
	trem1: "34 0 0 0 0",
	trem2: "34 0 0 0 0",
	trem3: "34 0 0 0 0",
	trem4: "34 0 0 0 0",
	xstem: "35 0 0 0 0",
	beambr1: "36 0 0 0 0",
	beambr2: "36 0 0 0 0",
	rbstop: "37 0 0 0 0",
	"/": "38 0 0 6 6",
	"//": "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",
	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",
	"tie(": "44 0 0 0 0",
	"tie)": "44 0 0 0 0",
	fg: "45 0 0 0 0"},

	// types of decoration per function
	f_near = [
		d_near,		// 0 - near the note
		d_slide,	// 1 - slide or tied to the note stem
		d_arp		// 2 - arpeggio
	],
	f_note = [
		null, null, null, null,
		d_upstaff	// 4 (below the staff)
	],
	f_staff = [
		null, null, null,
		d_upstaff,	// 3 - tied to note
		null,
		d_upstaff,	// 5 (above the staff)
		d_upstaff,	// 6 - tied to staff (dynamic marks)
		d_upstaff	// 7 (below the staff)
	]

/* -- get the max/min vertical offset -- */
function y_get(st, up, x, w) {
	var	y,
		p_staff = staff_tb[st],
	i = (x / 2) | 0,
	j = ((x + w) / 2) | 0

	if (i < 0) {
		i = 0
		if (j < 0)
			j = 0
	}
	if (j >= YSTEP) {
		j = YSTEP - 1
		if (i > j)
			i = j
	}
	if (up) {
		y = p_staff.top[i++]
		while (i <= j) {
			if (y < p_staff.top[i])
				y = p_staff.top[i];
			i++
		}
	} else {
		y = p_staff.bot[i++]
		while (i <= j) {
			if (y > p_staff.bot[i])
				y = p_staff.bot[i];
			i++
		}
	}
	return y
}

/* -- adjust the vertical offsets -- */
function y_set(st, up, x, w, y) {
    var	p_staff = staff_tb[st],
	i = (x / 2) | 0,
	j = ((x + w) / 2) | 0

	/* (may occur when annotation on 'y' at start of an empty staff) */
	if (i < 0) {
		i = 0
		if (j < 0)
			j = 0
	}
	if (j >= YSTEP) {
		j = YSTEP - 1
		if (i > j)
			i = j
	}
	if (up) {
		while (i <= j) {
			if (p_staff.top[i] < y)
				p_staff.top[i] = y;
			i++
		}
	} else {
		while (i <= j) {
			if (p_staff.bot[i] > y)
				p_staff.bot[i] = y;
			i++
		}
	}
}

// get the staff position
// - of the ornaments
function up3(s, pos) {
	switch (pos & 0x07) {
	case C.SL_ABOVE:
		return 1	// true
	case C.SL_BELOW:
		return 0	// false
	}
	return s.multi > 0 || !s.second
} // up3()

// - of the dynamic and volume marks
function up6(s, pos) {
	switch (pos & 0x07) {
	case C.SL_ABOVE:
		return true
	case C.SL_BELOW:
		return false
	}
	if (s.multi)
		return s.multi > 0
	if (!s.p_v.have_ly)
		return false

	/* above if the lyrics are below the staff */
	return (s.pos.voc & 0x07) != C.SL_ABOVE
}

/* -- drawing functions -- */
/* 2: special case for arpeggio */
function d_arp(de) {
	var	m, h, dx,
		s = de.s,
		dd = de.dd,
		xc = dd.wr

	if (s.type == C.NOTE) {
		for (m = 0; m <= s.nhd; m++) {
			if (s.notes[m].acc) {
				dx = s.notes[m].shac
			} else {
				dx = 1 - s.notes[m].shhd
				switch (s.head) {
				case C.SQUARE:
					dx += 3.5
					break
				case C.OVALBARS:
				case C.OVAL:
					dx += 2
					break
				}
			}
			if (dx > xc)
				xc = dx
		}
	}
	h = 3 * (s.notes[s.nhd].pit - s.notes[0].pit) + 4;
	m = dd.h			/* minimum height */
	if (h < m)
		h = m;

	de.has_val = true;
	de.val = h;
//	de.x = s.x - xc;
	de.x -= xc;
	de.y = 3 * ((s.notes[0].pit + s.notes[s.nhd].pit) / 2 - 18) - h / 2 - 3
}

/* 0: near the note (dot, tenuto) */
function d_near(de) {
	var	y,
		up = de.up,
		s = de.s,
		dd = de.dd

	y = up ? s.ymx : s.ymn
	if (y > 0 && y < 24) {
		y = (((y + 9) / 6) | 0) * 6 - 6	// between lines
	}
	if (up) {
		if (s.ys > 27
		 && dd.name[0] == 'd'		// if dot (staccato)
		 && s.a_dd[0].name == "dot"	// as the first decoration
		 && s.stem > 0 && s.nflags >= 0
		 && s.beam_st && s.beam_end)
			y -= 6			// put the dot a bit lower
		else
			y += dd.hd
		if (s.ymx < y + dd.h)
			s.ymx = y + dd.h
	} else if (dd.name[0] == 'w') {		// wedge (no descent)
		de.inv = true
		y -= dd.h
		s.ymn = y
	} else {
		y -= dd.h
		s.ymn = y - dd.hd
	}
	de.x -= dd.wl
	de.y = y
	if (s.type == C.NOTE)
		de.x += s.notes[s.stem >= 0 ? 0 : s.nhd].shhd
}

/* 1: special case for slide */
function d_slide(de) {
    var	m, dx, xc, yc,
	s = de.s

	if (s.decstm != null) {			// decoration tied to the stem
		if (de.s.stem >= 0) {
			if (s.nflags >= -1) {
				xc = 3.5
				yc = s.ys
				if (s.nflags > 1)
					yc -= 4 * (s.nflags - 1)
			} else {
				xc = 0
				yc = s.y + 21
			}
			de.y = (yc + 3 * (s.notes[s.nhd].pit - 18)) / 2
		} else {
			de.rotpi = 1//true	// rotate pi (180°)
			if (s.nflags >= -1) {
				xc = -3.5
				yc = s.ys
				if (s.nflags > 1)
					yc += 4 * (s.nflags - 1)
			} else {
				xc = 0
				yc = s.y - 21
			}
			de.y = (yc + 3 * (s.notes[0].pit - 18)) / 2
		}
	} else {
		xc = -5
		de.y = 3 * (s.notes[0].pit - 18)
		if (de.dd.glyph == "sld") {		// !slide!
			xc = -10
			for (m = 0; m <= s.nhd; m++) {
				if (s.notes[m].acc) {
					dx = -7 - s.notes[m].shac
				} else {
					dx = -10 + s.notes[m].shhd
					switch (s.head) {
					case C.SQUARE:
						dx -= 3.5
						break
					case C.OVALBARS:
					case C.OVAL:
						dx -= 2
						break
					}
				}
				if (dx < xc)
					xc = dx
			}
		}
	}
	de.x += xc

	if (de.y < 0)
		y_set(s.st, 0, de.x, de.dd.wl, de.y - de.dd.h)
}

// special case for long decoration
function d_trill(de) {
	if (de.ldst)
		return
    var	y, w, tmp,
	dd = de.dd,
	de2 = de.prev,
	up = de.start.up,
		s2 = de.s,
		st = s2.st,
		s = de.start.s,
		x = s.x

	// shift the starting point of a long decoration
	// in the cases "T!trill(!" and "!pp!!<(!"
	// (side effect on x)
	function sh_st() {
	    var	de3,
		de2 = de.start,			// start of the decoration
		s = de2.s,
		i = de2.ix			// index of the current decoration

		while (--i >= 0) {
			de3 = a_de[i]
			if (!de3 || de3.s != s)
				break
		}
		while (1) {			// loop on the decorations of the symbol
			i++
			de3 = a_de[i]
			if (!de3 || de3.s != s)
				break
			if (de3 == de2)
				continue
			if (!(up ^ de3.up)
			 && (de3.dd.name == "trill"
			  || de3.dd.func == 6)) {	// dynamic
				x += de3.dd.wr + 2
				break
			}
		}
	} // sh_st()

	// shift the ending point of a long decoration
	// (side effect on w)
	function sh_en() {
	    var	de3,
		i = de.ix			// index of the current decoration

		while (--i > 0) {
			de3 = a_de[i]
			if (!de3 || de3.s != s2)
				break
		}
		while (1) {			// loop on the decorations of the symbol
			i++
			de3 = a_de[i]
			if (!de3 || de3.s != s2)
				break
//			if (de3 == de || de3 == de2)
			if (de3 == de)
				continue
			if (!(up ^ de3.up)
			 && de3.dd.func == 6) {	// if dynamic mark
				w -= de3.dd.wl
				break
			}
		}
	} //sh_en()

	// d_trill()
	if (de2) {			// same height
		x = de2.s.x + de.dd.wl + 2
		de2.val -= de2.dd.wr
		if (de2.val < 8)
			de2.val = 8
	}
	de.st = st
	de.up = up

	sh_st()				// shift the starting point?

	if (de.defl.noen) {		/* if no decoration end */
		w = de.x - x
		if (w < 20) {
			x = de.x - 20 - 3;
			w = 20
		}
	} else {
		w = s2.x - x - 4
		sh_en(de)		// shift the ending point?
		if (w < 20)
			w = 20
	}
	y = y_get(st, up, x - dd.wl, w)
	if (up) {
		tmp = staff_tb[s.st].topbar + 2
		if (y < tmp)
			y = tmp
	} else {
		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
	if (up)
		y += dd.h;
	else
		y -= dd.hd
	y_set(st, up, x, w, y)
	if (up)
		s.ymx = s2.ymx = y
	else
		s.ymn = s2.ymn = y
}

/* 3, 4, 5, 7: above (or below) the staff */
function d_upstaff(de) {

	// don't treat here the long decorations
	if (de.ldst)			// if long deco start
		return
	if (de.start) {			// if long decoration
		d_trill(de)
		return
	}

    var	y, inv,
	up = de.up,
	s = de.s,
	dd = de.dd,
	x = de.x,
	w = dd.wl + dd.wr

	// glyphs inside the staff
	switch (dd.glyph) {
	case "lphr":
	case "mphr":
	case "sphr":
	case "short":
	case "tick":
		if (s.type == C.BAR)
			s.invis = 1
		// fall thru
	case "brth":
	case "caes":
		y = staff_tb[s.st].topbar + 2 + dd.hd
		if (!s.invis) {
			if (dd.glyph == "brth" && y < s.ymx)
				y = s.ymx
			for (s = s.ts_next; s; s = s.ts_next)
				if (s.seqst)
					break
			x += ((s ? s.x : realwidth) - x) * .45
		}
		de.x = x
		de.y = y
		return
	}

	if (s.nhd)
		x += s.notes[s.stem >= 0 ? 0 : s.nhd].shhd;

	switch (dd.ty) {
	case '@':
	case '<':
	case '>':
		y = de.y
		break
	}
	if (y == undefined) {
		if (up) {
			y = y_get(s.st, true, x - dd.wl, w)
					+ dd.hd
			if (de.y > y)
				y = de.y
			s.ymx = y + dd.h
		} else {
			y = y_get(s.st, false, x - dd.wl, w)
				- dd.h
			if (de.y < y)
				y = de.y
			if (dd.name == "fermata"
			 || dd.glyph == "accent"
			 || dd.glyph == "roll")
				de.inv = 1
			s.ymn = y - dd.hd
		}
	}

	if (dd.wr > 5 && x > realwidth - dd.wr)
		de.x = x = realwidth - dd.wr

//    if (dd.func == 6
//     && ((de.pos & C.SL_ALI_MSK) == C.SL_ALIGN
//      || ((de.pos & C.SL_ALI_MSK) == 0
//       && de.s.fmt.dynalign > 0)))	// if align
//	;
//    else
	if (up)
		y_set(s.st, 1, x - dd.wl, w, y + dd.h)
	else
		y_set(s.st, 0, x - dd.wl, w, y - dd.hd)

	de.y = y
}

// add a decoration
/* syntax:
 *	%%deco <name> <c_func> <glyph> <h> <wl> <wr> [<str>]
 * "<h>" may be followed by ",<hd>" (descent)
 */
function deco_add(param) {
	var dv = param.match(/(\S*)\s+(.*)/);
	decos[dv[1]] = dv[2]
}

// define a decoration
// nm is the name of the decoration
// nmd is the name of the definition in the table 'decos'
function deco_def(nm, nmd) {
	if (!nmd)
		nmd = nm
    var a, dd, dd2, nm2, c, i, elts, str, hd,
	text = decos[nmd]

	// check if a long decoration with number
	if (!text) {
		if (/\d[()]$/.test(nmd))
			text = decos[nmd.replace(/\d/, '')]
		else if (/^\d$/.test(nmd))	// or some fingering/string number
			text = "3 fng 5,5 3 3 " + nmd
	}
	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.]+)/)
	if (!a) {
		error(1, null, "Invalid decoration '$1'", nm)
		return //undefined
	}
	var	c_func = Number(a[1]),
//		glyph = a[2],
		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 > 10
	 && (c_func < 32 || c_func > 45)) {
		error(1, null, "%%deco: bad C function index '$1'", c_func)
		return //undefined
	}
//	if (c_func == 5)			// old !trill(!
//		c_func = 3
//	if (c_func == 7)			// old !cresc(!
//		c_func = 6

	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
	}

	// create/redefine the decoration
	dd = dd_tb[nm]
	if (!dd) {
		dd = {
			name: nm
		}
		dd_tb[nm] = dd
	}

	/* set the values */
	dd.func = nm.indexOf("head-") == 0 ? 9 : c_func;
	dd.glyph = a[2];
	dd.h = Number(h)
	dd.hd = Number(hd)
	dd.wl = wl;
	dd.wr = wr;
	str = text.replace(a[0], '').trim()
	if (str) {				// optional string
		if (str[0] == '"')
			str = str.slice(1, -1);
		if (str[0] == '@') {
			c = str.match(/^@([0-9.-]+),([0-9.-]+);?/)
		    if (!c) {
			error(1, null, "%%deco: bad position '$1'", str)
			return
		    }
			dd.dx = +c[1]		// x and y offsets
			dd.dy = +c[2]
			str = str.replace(c[0], '')
		}
		dd.str = str
	}

	/* compatibility */
	if (dd.func == 6 && dd.str == undefined)
		dd.str = nm

	// link the start and end of long decorations
	c = nm.slice(-1)
	if (c == '(' ||
	    (c == ')' && nm.indexOf('(') < 0)) {	// not (#)
		dd.str = null;			// (no string)
		nm2 = nm.slice(0, -1) + (c == '(' ? ')' : '(');
		dd2 = dd_tb[nm2]
		if (dd2) {
			if (c == '(') {
				dd.dd_en = dd2;
				dd2.dd_st = dd
			} else {
				dd.dd_st = dd2;
				dd2.dd_en = dd
			}
		}
	}
	return dd
}

// define a cross-voice tie
// @nm = decoration name
// @s = note symbol
// @nt1 = note
function do_ctie(nm, s, nt1) {
    var	nt2 = cross[nm],
	nm2 = nm.slice(0, -1) + (nm.slice(-1) == '(' ? ')' : '(')

	if (nt2) {
		error(1, s, "Conflict on !$1!", nm)
		return
	}
	if (nt1.tie_ty)			// if normal '-'
		curvoice.tie_s = null

	nt1.s = s
	nt2 = cross[nm2]
	if (!nt2) {
		cross[nm] = nt1		// keep the start/end
		return
	}
	if (nm.slice(-1) == ')') {
		nt2 = nt1
		nt1 = cross[nm2]
	}
	cross[nm2] = null
	if (nt1.midi != nt2.midi
	 || nt1.s.time + nt1.dur != nt2.s.time) {
		error(1, s, "Bad tie")
	} else {
		if (!nt1.tie_ty)		// if not normal '-'
			nt1.tie_ty = C.SL_AUTO
		nt1.tie_e = nt2
		nt2.tie_s = nt1
		nt1.s.ti1 = nt2.s.ti2 = true
	}
} // do_ctie()

// get/create the definition of a decoration
function get_dd(nm) {
    var	ty, p,
	dd = dd_tb[nm]

	if (dd)
		return dd
	if ("<>^_@".indexOf(nm[0]) >= 0	// if position
	 && !/^([>^]|[<>]\d?[()])$/.test(nm)) {
		ty = nm[0]
		if (ty == '@') {
			p = nm.match(/@([-\d]+),([-\d]+)/)
			if (p)
				ty = p[0]
			else
				ty = ''		// accept decorations starting with '@'
		}
		dd = deco_def(nm, nm.replace(ty, ''))
	} else {
		dd = deco_def(nm)
	}
	if (!dd)
		return
	if (ty) {
		if (ty[0] == '@') {		// if with x,y
			dd.x = Number(p[1])
			dd.y = Number(p[2])
			ty = '@'
		}
		dd.ty = ty
	}
	return dd
} // get_dd()

/* -- convert the decorations -- */
function deco_cnv(s, prev) {
    var	i, j, dd, nm, note, s1, court, fg

	// mark a finger glissando
	function sav_fg() {
	    var	i,
		s1 = prev

		if (s.type != C.NOTE)
			return 1
		while (s1 && s1.type != C.NOTE)
			s1 = s1.prev
		if (!s1)
			return 1
		for (i = 0; i < s1.a_dd.length; i++) {
			if (s1.a_dd[i].name == dd.name) {
				if (!s.fg)
					s.fg = []
				s.fg.push({
					ty: 1,		// end of glissando
					s: s1,
					nm: dd.name
				})
				if (!s1.fg)
					s1.fg = []
				s1.fg.push({
					ty: 0,		// start of glissando
					s: s,
					nm: dd.name
				})
				return 0
			}
		}
		return 1
	} // sav_fg()

	while (1) {
		nm = a_dcn.shift()
		if (!nm)
			break
		dd = get_dd(nm)
		if (!dd)
			continue

		/* special decorations */
		switch (dd.func) {
		case 0:			// near
			if (s.type == C.BAR && nm == "dot") {
				s.bar_dotted = true
				continue
			}
			// fall thru
		case 1:			// slide & deco on stem
			if (dd.glyph[0] == '|')
				s.decstm = dd.h		// deco on stem
			// fall thru
		case 2:			// arp
//			if (s.type != C.NOTE && s.type != C.REST) {
			if (!s.notes) {
				error(1, s, errs.must_note_rest, nm)
				continue
			}
			break
		case 3:
			if (fg && dd.glyph == "fng") { // move the fingers out of staves
				for (i = 0; i <= 5; i++) {
					decos[i.toString()] = "5 fng 5,5 3 3 " + i
					if (dd_tb[i.toString()])
						dd_tb[i.toString()].func = 5
				}
			}
			break
		case 4:			// below the staff
		case 5:			// above the staff
			i = nm.match(/1?[85]([vm])([ab])([()])/)
			if (i) {				// if ottava
				j = i[1] == 'v' ? 1 : 2
				if (i[2] == 'b')
					j = -j
				if (!s.ottava)
					s.ottava = []
				s.ottava[i[3] == '(' ? 0 : 1] = j
				glovar.ottava = 1 //true
			}
			break
		case 8:			// gliss
			if (s.type != C.NOTE) {
				error(1, s, errs.must_note, nm)
				continue
			}
			note = s.notes[s.nhd] // move to the upper note of the chord
			if (!note.a_dd)
				note.a_dd = []
			note.a_dd.push(dd)
			continue
		case 9:			// alternate head
			if (!s.notes) {
				error(1, s, errs.must_note_rest, nm)
				continue
			}

			// move the alternate head of the chord to the notes
			for (j = 0; j <= s.nhd; j++) {
				note = s.notes[j]
				note.invis = true
				if (!note.a_dd)
					note.a_dd = []
				note.a_dd.push(dd)
			}
			continue
		case 10:		/* color */
			if (s.notes) {
				for (j = 0; j <= s.nhd; j++)
					s.notes[j].color = nm
			} else {
				s.color = nm
			}
			break
		case 32:		/* invisible */
			s.invis = true
			break
		case 33:		/* beamon */
			if (s.type != C.BAR) {
				error(1, s, "!beamon! must be on a bar")
				continue
			}
			s.beam_on = true
			break
		case 34:		/* trem1..trem4 */
			if (s.type != C.NOTE
			 || !prev
			 || prev.type != C.NOTE
			 || s.dur != prev.dur) {
				error(1, s,
					"!$1! must be on the last of a couple of notes",
					nm)
				continue
			}
			s.trem2 = true;
			s.beam_end = true;
			s.beam_st = false;
			prev.beam_st = true;
			prev.beam_end = false;
			s.ntrem = prev.ntrem = Number(nm[4]);
			for (j = 0; j <= s.nhd; j++)
				s.notes[j].dur *= 2;
			for (j = 0; j <= prev.nhd; j++)
				prev.notes[j].dur *= 2
			break
		case 35:		/* xstem */
			if (s.type != C.NOTE) {
				error(1, s, errs.must_note, nm)
				continue
			}
			s.xstem = true;
			break
		case 36:		/* beambr1 / beambr2 */
			if (s.type != C.NOTE) {
				error(1, s, errs.must_note, nm)
				continue
			}
			if (nm[6] == '1')
				s.beam_br1 = true
			else
				s.beam_br2 = true
			break
		case 37:		/* rbstop */
			s.rbstop = 1	// open
			break
		case 38:		/* /, // and /// = tremolo */
			if (s.type != C.NOTE) {
				error(1, s, errs.must_note, nm)
				continue
			}
			s.trem1 = true;
			s.ntrem = nm.length	/* 1, 2 or 3 */
			break
		case 39:		/* beam-accel/beam-rall */
			if (s.type != C.NOTE) {
				error(1, s, errs.must_note, nm)
				continue
			}
			s.feathered_beam = nm[5] == 'a' ? 1 : -1;
			break
		case 40:		/* stemless */
			s.stemless = true
			break
		case 41:		/* rbend */
			s.rbstop = 2	// with end
			break
		case 42:		// editorial
			if (s.type != C.NOTE) {
				error(1, s, errs.must_note, nm)
				continue
			}
			if (!s.notes[0].acc)
				continue
			nm = "sacc" + s.notes[0].acc.toString() // small accidental
			dd = dd_tb[nm]
			if (!dd) {
				dd = deco_def(nm)
				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
			if (s.type != C.NOTE) {
				error(1, s, errs.must_note, nm)
				continue
			}
			j = curvoice.acc[s.notes[0].pit + 19]
			if (s.notes[0].acc || !j)
				continue
			court = 1			// defer
			break
		case 44:		// cross-voice ties
			if (s.type != C.NOTE) {
				error(1, s, errs.must_note, nm)
				continue
			}
			do_ctie(nm, s, s.notes[0])	// (only one note for now)
			continue
		case 45:		// finger glissando
			fg = 1 //true
			continue
//		default:
//			break
		}

		// handle the fingering in case finger glissando
		if (fg && dd.glyph == 'fng') {
			fg = 0 //false
			if (sav_fg()) {
				error(1, s,
					"!$1! must be on the last of a couple of notes",
					nm)
				continue
			}
		}

		// add the decoration in the symbol
		if (!s.a_dd)
			s.a_dd = []
		s.a_dd.push(dd)
	}
	// handle the possible courtesy accidental
	if (court) {
		a_dcn.push("cacc" + j)
		dh_cnv(s, s.notes[0])
	}
}

// -- convert head decorations --
// The decorations are in the global array a_dcn
function dh_cnv(s, nt) {
    var	k, nm, dd

	while (1) {
		nm = a_dcn.shift()
		if (!nm)
			break
		dd = get_dd(nm)
		if (!dd)
			continue

		switch (dd.func) {
		case 1:			// slide
		case 3:
		case 8:			// gliss
			break
		default:
			error(1, s, "Cannot have !$1! on a head", nm)
			continue
		case 9:			// head replacement
			nt.invis = true
			break
		case 32:		// invisible
			nt.invis = true
			continue
		case 10:		// color
			nt.color = nm
			continue
		case 40:		// stemless chord (abcm2ps behaviour)
			s.stemless = true
			continue
		case 44:		// cross-voice ties
			do_ctie(nm, s, nt)
			continue
		}

		// add the decoration in the note
		if (!nt.a_dd)
			nt.a_dd = []
		nt.a_dd.push(dd)
	}
} // dh_cnv()

/* -- update the x position of a decoration -- */
// used to center the rests
function deco_update(s, dx) {
	var	i, de,
		nd = a_de.length

	for (i = 0; i < nd; i++) {
		de = a_de[i]
		if (de.s == s)
			de.x += dx
	}
}

/* -- adjust the symbol width -- */
function deco_width(s, wlnt) {
    var	dd, i, w,
	wl = wlnt,
	wr = s.wr,
		a_dd = s.a_dd,
		nd = a_dd.length

	for (i = 0; i < nd; i++) {
		dd =  a_dd[i]
		switch (dd.func) {
		case 1:			/* slide */
		case 2:			/* arpeggio */
			if (wl < 12)
				wl = 12
			break
		case 3:
			switch (dd.glyph) {
			case "brth":
			case "lphr":
			case "mphr":
			case "sphr":
				if (s.wr < 20)
					s.wr = 20
				break
			default:
				w = dd.wl + 2
				if (wl < w)
					wl = w
				break
			}
			// fall thru
		default:
			switch (dd.ty) {
			case '<':
				w = wlnt + dd.wl + dd.wr + 6
				if (wl < w)
					wl = w
				break
			case '>':
				w = wr + dd.wl + dd.wr + 6
				if (s.wr < w)
					s.wr = w
				break
			}
			break
		}
	}
	return wl
}

// compute the width of decorations in chord
function deco_wch(nt) {
    var	i, w, dd,
	wl = 0,
	n = nt.a_dd.length

	for (i = 0; i < n; i++) {
		dd = nt.a_dd[i]
		w = dd.wl + dd.wr
		if (nt.shac)
			w += nt.shac
		if (w > wl)
			wl = w
	}
	return wl
} // deco_wch()

/* -- draw the decorations -- */
/* (the staves are defined) */
Abc.prototype.draw_all_deco = function() {
	if (!a_de.length)
		return
	var	de, dd, s, note, f, st, x, y, y2, ym, uf, i, str, a,
		new_de = [],
		ymid = []

	// display a finger glissando
	function out_fg() {
	    var	k, l, de2, fg, fg2, x2,
		j = s.fg.length

		while (--j >= 0) {
			fg = s.fg[j]
			if (fg.nm == dd.name)
				break
		}
		if (j < 0)
			return

		if (fg.ty) {		// end
			if (fg.ty == 1)		// no start (not treated yet)
				out_wln(x - 19, y, 12)
			return
		}

		x2 = x + 7		// start
		for (k = 0; k < a_de.length; k++) {
			de2 = a_de[k]
			if (de2.s != fg.s
			 || de2.dd.name != dd.name)
				continue
			for (l = 0; l < de2.s.fg.length; l++) {
				fg2 = de2.s.fg[l]
				if (fg2.nm == fg.nm)
					break
			}
			if (fg2.nm == fg.nm) {		// if same finger
				fg2.ty = 2		// end done
				xypath(x2, y + 1)
				output += 'l' + (de2.x - 7 - x2).toFixed(1)
					+ ' ' + (y - de2.y
						- staff_tb[s.st].y).toFixed(1)
					+ '" stroke-width=".7"/>\n'
				return
			}
		}
		out_wln(x2, y, 12)	// start without end
	} // out_fg()

		st = nstaff;
		y = staff_tb[st].y
		while (--st >= 0) {
			y2 = staff_tb[st].y;
			ymid[st] = (y + 24 + y2) * .5;
			y = y2
		}

	while (1) {
		de = a_de.shift()
		if (!de)
			break
		dd = de.dd
		if (!dd)
			continue		// deleted

		if (dd.dd_en)			// start of long decoration
			continue

		// handle the stem direction
		s = de.s
		f = dd.glyph;
		i = f.indexOf('/')
		if (i > 0) {
			if (s.stem >= 0)
				f = f.slice(0, i)
			else
				f = f.slice(i + 1)
		}

		// no voice scale if staff decoration
		if (f_staff[dd.func])
			set_sscale(s.st)
		else
			set_scale(s);

		st = de.st;
		if (!staff_tb[st].topbar)
			continue		// invisible staff
		x = de.x + (dd.dx || 0)
		y = de.y + staff_tb[st].y + (dd.dy || 0)

		/* center the dynamic marks between two staves */
/*fixme: KO when deco on other voice and same direction*/
		if (dd.func == 6
			&& ((de.pos & C.SL_ALI_MSK) == C.SL_CENTER
			 || ((de.pos & C.SL_ALI_MSK) == 0
			  && !s.fmt.dynalign))
			&& ((de.up && st > 0)
			 || (!de.up && st < nstaff))) {
			if (de.up)
				ym = ymid[--st]
			else
				ym = ymid[st++];
			ym -= dd.h * .5
			if ((de.up && y < ym)
			 || (!de.up && y > ym)) {
//				if (s.st > st) {
//					while (s.st != st)
//						s = s.ts_prev
//				} else if (s.st < st) {
//					while (s.st != st)
//						s = s.ts_next
//				}
				y2 = y_get(st, !de.up, de.x, de.val)
					+ staff_tb[st].y
				if (de.up)
					y2 -= dd.h
//fixme: y_set is not used later!
				if ((de.up && y2 > ym)
				 || (!de.up && y2 < ym)) {
					y = ym;
//					y_set(st, de.up, de.x, de.val,
//						(de.up ? y + dd.h : y)
//							- staff_tb[st].y)
					if (stv_g.scale != 1)
						y += stv_g.dy / 2
				}
			}
		}

		// check if user JS decoration
		if (user.deco) {
			uf = user.deco[f]
			if (uf && typeof(uf) == "function") {
				uf.call(self, x, y, de)
				continue
			}
		}

		// check if user PS definition
		if (self.psdeco(x, y, de))
			continue

		anno_start(s, 'deco')
//		if (de.flags.grace) {
//			g_open(x, y, 0, .7, de.inv ? -.7 : 0);
//			x = y = 0
//		} else
		if (de.inv) {
			y = y + dd.h - dd.hd
			g_open(x, y, 0, 1, -1);
			x = y = 0
		} else if (de.rotpi) {
			g_open(x, y, 180)
			x = y = 0
		}
		if (de.has_val) {
			if (dd.func != 2	// if not !arpeggio!
			 || stv_g.st < 0)	// or not staff scale
// || voice_tb[s.v].scale != 1)
				out_deco_val(x, y, f, de.val / stv_g.scale, de.defl)
			else
				out_deco_val(x, y, f, de.val, de.defl)
			if (de.cont)
				new_de.push(de.start)	// to be continued next line
		} else if (dd.str != undefined		// string
			&& !tgls[dd.glyph]
			&& !glyphs[dd.glyph]) {		// with a class
			if (s.fg)			// if finger glissando
				out_fg()		// (may change y)
			out_deco_str(x, y,		// - dd.h * .2,
					de)
		} else if (de.lden) {
			out_deco_long(x, y, de)
		} else {
			xygl(x, y, f)
		}
		if (stv_g.g)
			g_close();
		anno_stop(s, 'deco')
	}

	// keep the long decorations which continue on the next line
	a_de = new_de
}

/* -- create the decorations and define the ones near the notes -- */
/* (the staves are not yet defined) */
/* (delayed output) */
/* this function must be called first as it builds the deco element table */
function draw_deco_near() {
    var	s, g

	// update starting old decorations
	function ldeco_update(s) {
		var	i, de,
//			x = s.ts_prev.x + s.ts_prev.wr
			x = s.x - s.wl,
			nd = a_de.length

		for (i = 0; i < nd; i++) {
			de = a_de[i];
			de.ix = i;
			de.s.x = de.x = x;
			de.defl.nost = true
		}
	}

	/* -- create the deco elements, and treat the near ones -- */
	function create_deco(s) {
	    var	dd, k, pos, de, x, y, up,
		nd = s.a_dd.length

		if (s.y == undefined)
			s.y = 0			// (no y in measure bars)

/*fixme:pb with decorations above the staff*/
		for (k = 0; k < nd; k++) {
			dd = s.a_dd[k]

			// adjust the position
			x = s.x
			y = s.y
			switch (dd.func) {
			default:
				if (dd.func >= 10)
					continue
				pos = 0
				break
			case 3:				/* d_upstaff */
			case 4:
			case 5:				// after slurs
				pos = s.pos.orn
				break
			case 6:				/* dynamic */
				pos = s.pos.dyn
				break
			}

			switch (dd.ty) {		// explicit position
			case '^':
				pos = (pos & ~0x07) | C.SL_ABOVE
				break
			case '_':
				pos = (pos & ~0x07) | C.SL_BELOW
				break
			case '<':
			case '>':
				pos = (pos & 0x07) | C.SL_CLOSE
				if (dd.ty == '<') {
					x -= dd.wr + 8
					if (s.notes[0].acc)
						x -= 8
				} else {
					x += dd.wl + 8
				}
				y = 3 * (s.notes[0].pit - 18)
						- (dd.h - dd.hd) / 2
				break
			case '@':
				x += dd.x
				y += dd.y
				break
			}

			if ((pos & 0x07) == C.SL_HIDDEN)
				continue

			de = {
				s: s,
				dd: dd,
				st: s.st,
				ix: a_de.length,
				defl: {},
				x: x,
				y: y
			}
			if (pos)
				de.pos = pos

			up = 0 //false
			if (dd.ty == '^') {
				up = 1 //true
			} else if (dd.ty == '_') {
				;
			} else {
				switch (dd.func) {
				case 0:
					if (s.multi)
						up = s.multi > 0
					else
						up = s.stem < 0
					break
				case 3:
				case 5:
					up = up3(s, pos)
					break
				case 6:
					up = up6(s, pos)
					break
				}
			}
			de.up = up

			if (dd.name.indexOf("inverted") >= 0)
				de.inv = 1
			if (s.type == C.BAR && !dd.ty)
				de.x -= s.wl / 2 - 2
			a_de.push(de)
			if (dd.dd_en) {
				de.ldst = true
			} else if (dd.dd_st) {
//fixme: pb with "()"
				de.lden = true;
				de.defl.nost = true
			}

			if (f_near[dd.func])
				f_near[dd.func](de)
		}
	} // create_deco()

	// create the decorations of note heads
	function create_dh(s, m) {
	    var	de, k, dd,
		note = s.notes[m],
		nd = note.a_dd.length,
		x = s.x

		for (k = 0; k < nd; k++) {
			dd = note.a_dd[k]

//fixme: check if hidden?
			de = {
				s: s,
				dd: dd,
				st: s.st,
				m: m,
				ix: 0,
				defl: {},
				x: x,
				y: 3 * (note.pit - 18) - (dd.h - dd.hd) / 2
//				dy: 0
			}

			if (dd.ty) {		// if explicit position
				if (dd.ty == '@') {
					de.x += dd.x
					de.y += dd.y
				} else {
					de.y -= (dd.h - dd.hd) / 2	// center
					if (dd.ty == '<') {
						de.x -= dd.wr + 8
						if (s.notes[m].acc)
							x -= 8
					} else if (dd.ty == '>') {
						de.x += dd.wl + 8
					}
				}
			} else {
				if (note.shhd)
					de.x += note.shhd * stv_g.scale
				if (note.shac)
					de.x -= note.shac
				if (dd.func != 8)	// if not glissendo
					de.x -= dd.wl + dd.wr + 3
			}

			a_de.push(de)
			if (dd.dd_en) {
				de.ldst = true
			} else if (dd.dd_st) {
				de.lden = true;
				de.defl.nost = true
			}
		}
	} // create_dh()

	// create all decorations of a note (chord and heads)
	function create_all(s) {
		if (s.invis && s.play)	// play sequence: no decoration
			return
		if (s.a_dd)
			create_deco(s)
		if (s.notes) {
			for (var m = 0; m < s.notes.length; m++) {
				if (s.notes[m].a_dd)
					create_dh(s, m)
			}
		}
	} // create_all()

	// link the long decorations
	function ll_deco() {
	    var	i, j, de, de2, de3, dd, dd2, v, s, st,
			n_de = a_de.length

		// add ending decorations
		for (i = 0; i < n_de; i++) {
			de = a_de[i]
			if (!de.ldst)	// not the start of long decoration
				continue
			dd = de.dd;
			dd2 = dd.dd_en;
			s = de.s;
			v = s.v			// search later in the voice
			for (j = i + 1; j < n_de; j++) {
				de2 = a_de[j]
				if (!de2.start
				 && de2.dd == dd2 && de2.s.v == v)
					break
			}
			if (j == n_de) {	// no end, search in the staff
				st = s.st;
				for (j = i + 1; j < n_de; j++) {
					de2 = a_de[j]
					if (!de2.start
					 && de2.dd == dd2 && de2.s.st == st)
						break
				}
			}
			if (j == n_de) {	// no end, insert one
				de2 = {
					s: s,
					st: de.st,
					dd: dd2,
					ix: a_de.length - 1,
					x: realwidth - 6,
					y: s.y,
					cont: true,	// keep for next line
					lden: true,
					defl: {
						noen: true
					}
				}
				if (de2.x < s.x + 10)
					de2.x = s.x + 10
				if (de.m != undefined)
					de2.m = de.m;
				a_de.push(de2)
			}
			de2.start = de;
			de2.defl.nost = de.defl.nost

			// handle same decoration ending at a same time
			j = i
			while (--j >= 0) {
				de3 = a_de[j]
				if (!de3.start)
					continue
				if (de3.s.time < s.time)
					break
				if (de3.dd.name == de2.dd.name) {
					de2.prev = de3
					break
				}
			}
		}

		// add starting decorations
		for (i = 0; i < n_de; i++) {
			de2 = a_de[i]
			if (!de2.lden	// not the end of long decoration
			 || de2.start)	// start already found
				continue
			s = de2.s;
			de = {
				s: prev_scut(s),
				st: de2.st,
				dd: de2.dd.dd_st,
				ix: a_de.length - 1,
//				x: s.x - s.wl - 4,
				y: s.y,
				ldst: true
			}
			de.x = de.s.x + de.s.wr
			if (de2.m != undefined)
				de.m = de2.m;
			a_de.push(de);
			de2.start = de
		}
	} // ll_deco

	// update the long decorations started in the previous line
	for (s = tsfirst ; s; s = s.ts_next) {
		switch (s.type) {
		case C.CLEF:
		case C.KEY:
		case C.METER:
			continue
		}
		break
	}
	if (a_de.length)
		ldeco_update(s)

	for ( ; s; s = s.ts_next) {
		switch (s.type) {
		case C.BAR:
		case C.MREST:
		case C.NOTE:
		case C.REST:
		case C.SPACE:
			break
		case C.GRACE:
			for (g = s.extra; g; g = g.next)
				create_all(g)
			break
		default:
			continue
		}
		create_all(s)
	}
	ll_deco()			// link the long decorations
}

/* -- define the decorations tied to a note -- */
/* (the staves are not yet defined) */
/* (delayed output) */
function draw_deco_note() {
	var	i, de, dd, f,
		nd = a_de.length

	for (i = 0; i < nd; i++) {
		de = a_de[i];
		dd = de.dd;
		f = dd.func
		if (f_note[f]
		 && de.m == undefined)
			f_note[f](de)
	}
}

// -- define the music elements tied to the staff --
//	- decoration tied to the staves
//	- chord symbols
//	- repeat brackets
/* (the staves are not yet defined) */
/* (unscaled delayed output) */
function draw_deco_staff() {
    var	s, p_voice, y, i, v, de, dd, w,
	minmax = new Array(nstaff + 1),
	nd = a_de.length

	/* draw the repeat brackets */
	function draw_repbra(p_voice) {
		var s, s1, x, y, y2, i, p, w, wh, first_repeat;

		// search the max y offset of the line
		y = staff_tb[p_voice.st].topbar + 15	// 10 (vert bar) + 5 (room)
		for (s = p_voice.sym; s; s = s.next) {
			if (s.type != C.BAR)
				continue
			if (!s.rbstart || s.norepbra)
				continue
/*fixme: line cut on repeat!*/
			if (!s.next)
				break
			if (!first_repeat) {
				first_repeat = s;
				set_font("repeat")
			}
			s1 = s
			for (;;) {
				if (!s.next)
					break
				s = s.next
				if (s.rbstop)
					break
			}
			x = s1.x
			if (s1.xsh)			// volta shift
				x += s1.xsh
			y2 = y_get(p_voice.st, true, x, s.x - x) + 2
			if (y < y2)
				y = y2

			// have room for the vertical lines and the repeat numbers
			if (s1.rbstart == 2) {
				y2 = y_get(p_voice.st, true, x, 3) + 10
				if (y < y2)
					y = y2
			}
			if (s.rbstop == 2) {
				y2 = y_get(p_voice.st, true, s.x - 3, 3) + 10
				if (y < y2)
					y = y2
			}
			if (s1.text) {
				wh = strwh(s1.text);
				y2 = y_get(p_voice.st, true, x + 4, wh[0]) +
						wh[1]
				if (y < y2)
					y = y2
			}
			if (s.rbstart)
				s = s.prev
		}

		/* draw the repeat indications */
		s = first_repeat
		if (!s)
			return
		set_dscale(p_voice.st, true);
		y2 =  y * staff_tb[p_voice.st].staffscale
		for ( ; s; s = s.next) {
			if (!s.rbstart || s.norepbra)
				continue
			s1 = s
			while (1) {
				if (!s.next)
					break
				s = s.next
				if (s.rbstop)
					break
			}
			if (s1 == s)
				break
			x = s1.x
			if (s1.xsh)			// volta shift
				x += s1.xsh
			if (cfmt.measurenb > 0 & s.bar_num
			 && s.bar_num % cfmt.measurenb)
				x += 6
			if (s.type != C.BAR) {
				w = s.rbstop ? 0 : s.x - realwidth + 4
			} else if ((s.bar_type.length > 1	// if complex bar
				 && s.bar_type != "[]")
				|| s.bar_type == "]") {
//				if (s.bar_type == "]")
//					s.invis = true
//fixme:%%staves: cur_sy moved?
				if (s1.st > 0
				 && !(cur_sy.staves[s1.st - 1].flags & STOP_BAR))
					w = s.wl
				else if (s.bar_type.slice(-1) == ':')
					w = 12
				else if (s.bar_type[0] != ':')
//				      || s.bar_type == "]")
					w = 0		/* explicit repeat end */
				else
					w = 8
			} else {
				w = (s.rbstop && !s.rbstart) ? 0 : 8
			}
			w = (s.x - x - w)	// / staff_tb[p_voice.st].staffscale;

			if (!s.next		// 2nd ending at end of line
			 && !s.rbstop
			 && !p_voice.bar_start) { // continue on next line
				p_voice.bar_start = _bar(s)
				p_voice.bar_start.bar_type = ""
				p_voice.bar_start.rbstart = 1
			}
			if (s1.text)
				xy_str(x + 4, y2 - gene.curfont.size,
					s1.text);
			xypath(x, y2);
			if (s1.rbstart == 2)
				output += 'm0 10v-10';
			output+= 'h' + w.toFixed(1)
			if (s.rbstop == 2)
				output += 'v10';
			output += '"/>\n';
			y_set(s1.st, true, x, w, y + 2)

			if (s.rbstart)
				s = s.prev
		}
	} // draw_repbra()

	/* create the decorations tied to the staves */
	for (i = 0; i <= nstaff; i++)
		minmax[i] = {
			ymin: 0,
			ymax: 0
		}
	for (i = 0; i < nd; i++) {
		de = a_de[i];
		dd = de.dd
		if (!dd)		// if error
			continue
		if (!f_staff[dd.func]	/* if not tied to the staff */
		 || de.m != undefined	// or head decoration
		 || dd.ty == '<' || dd.ty == '>' || dd.ty == '@')
			continue

		f_staff[dd.func](de)
		if (dd.func != 6
		 || dd.dd_en)		// if start
			continue

		if ((de.pos & C.SL_ALI_MSK) == C.SL_ALIGN
		 || ((de.pos & C.SL_ALI_MSK) == 0
		  && de.s.fmt.dynalign > 0)) {	// if align
			if (de.up) {
				if (de.y > minmax[de.st].ymax)
					minmax[de.st].ymax = de.y
			} else {
				if (de.y < minmax[de.st].ymin)
					minmax[de.st].ymin = de.y
			}
		}
	}

	// set the same vertical offset of the dynamic marks
	for (i = 0; i < nd; i++) {
		de = a_de[i];
		dd = de.dd
		if (!dd)				// if error
			continue

		// if @x,y offsets, update the top and bottom of the staff
		if (dd.ty == '@') {
		    var	y2

			y = de.y
			if (y > 0) {
				y2 = y + dd.h + 2
				if (y2 > staff_tb[de.st].ann_top)
					staff_tb[de.st].ann_top = y2
			} else {
				y2 = y - dd.hd - 2
				if (y2 < staff_tb[de.st].ann_bot)
					staff_tb[de.st].ann_bot = y2

			}
			continue
		}
		if (dd.func != 6
		 || dd.ty == '<' || dd.ty == '>'
		 || dd.dd_en)				// if start
			continue

		w = de.val || (dd.wl + dd.wr)
		if ((de.pos & C.SL_ALI_MSK) == C.SL_ALIGN
		  || ((de.pos & C.SL_ALI_MSK) == 0
		   && de.s.fmt.dynalign > 0)) {		// if align
			if (de.up)
				y = minmax[de.st].ymax
			else
				y = minmax[de.st].ymin;
			de.y = y
		} else {
			y = de.y
		}
		if (de.up)
			y += dd.h;
		else
			y -= dd.hd
		y_set(de.st, de.up, de.x, w, y)
	}

	// second pass for pedal (under the staff)
	for (i = 0; i < nd; i++) {
		de = a_de[i]
		dd = de.dd
		if (!dd)			// if error
			continue
		if (dd.dd_en			// if start
		 || dd.name.slice(0, 3) != "ped")
			continue
		w = de.val || 10
		de.y = y_get(de.st, 0, de.x, w)
			- (dd.dd_st && cfmt.pedline ? 10 : dd.h)
		y_set(de.st, 0, de.x, w, de.y)	// (no descent)
	}

	draw_all_chsy()		// draw all chord symbols

	/* draw the repeat brackets */
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (p_voice.second || !p_voice.sym || p_voice.ignore)
			continue
		draw_repbra(p_voice)
	}
}

/* -- draw the measure bar numbers -- */
/* (scaled delayed output) */
function draw_measnb() {
	var	s, st, bar_num, x, y, w, any_nb, font_size, w0,
		sy = cur_sy

	/* search the top staff */
	for (st = 0; st <= nstaff; st++) {
		if (sy.st_print[st])
			break
	}
	if (st > nstaff)
		return				/* no visible staff */
	set_dscale(st)

	/* leave the measure numbers as unscaled */
	if (staff_tb[st].staffscale != 1) {
		font_size = get_font("measure").size;
		param_set_font("measurefont", "* " +
			(font_size / staff_tb[st].staffscale).toString())
	}
	set_font("measure");
	w0 = cwidf('0');			// (greatest) width of a number

	s = tsfirst;				/* clef */
	bar_num = gene.nbar
	if (bar_num > 1) {
		if (cfmt.measurenb == 0) {
			any_nb = true;
			y = y_get(st, true, 0, 20)
			if (y < staff_tb[st].topbar + 14)
				y = staff_tb[st].topbar + 14;
			xy_str(0, y - gene.curfont.size * .2, 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.CLEF:
				case C.KEY:
				case C.METER:
				case C.STBRK:
					continue
				}
				break
			}

			// don't display the number twice
		     if (s.type != C.BAR || !s.bar_num) {
			any_nb = true;
			w = w0
			if (bar_num >= 10)
				w *= bar_num >= 100 ? 3 : 2
			if (gene.curfont.pad)
				w += gene.curfont.pad * 2
			x = (s.prev
				? s.prev.x + s.prev.wr / 2
				: s.x - s.wl) - w
			y = y_get(st, true, x, w) + 5
			if (y < staff_tb[st].topbar + 6)
				y = staff_tb[st].topbar + 6;
			y += gene.curfont.pad
			xy_str(x, y - gene.curfont.size * .2, bar_num.toString())
			y += gene.curfont.size + gene.curfont.pad
			y_set(st, true, x, w, y);
//			s.ymx = y
		     }
		}
	}

	for ( ; s; s = s.ts_next) {
		switch (s.type) {
		case C.STAVES:
			sy = s.sy
			for (st = 0; st < nstaff; st++) {
				if (sy.st_print[st])
					break
			}
			set_dscale(st)
			continue
		default:
			continue
		case C.BAR:
			if (!s.bar_num || s.bar_num <= 1)
				continue
			break
		}

		bar_num = s.bar_num
		if (cfmt.measurenb == 0
		 || (bar_num % cfmt.measurenb) != 0
		 || !s.next
		 || s.bar_mrep)
			continue
		if (!any_nb)
			any_nb = true;
		w = w0
		if (bar_num >= 10)
			w *= bar_num >= 100 ? 3 : 2
		if (gene.curfont.pad)
			w += gene.curfont.pad * 2
		x = s.x
		y = y_get(st, true, x, w)
		if (y < staff_tb[st].topbar + 6)
			y = staff_tb[st].topbar + 6
		if (s.next.type == C.NOTE) {
			if (s.next.stem > 0) {
				if (y < s.next.ys - gene.curfont.size)
					y = s.next.ys - gene.curfont.size
			} else {
				if (y < s.next.y)
					y = s.next.y
			}
		}
		y += 2 + gene.curfont.pad
		xy_str(x, y - gene.curfont.size * .2, bar_num.toString())
		y += gene.curfont.size + gene.curfont.pad
		y_set(st, true, x, w, y);
//		s.ymx = y
	}
	gene.nbar = bar_num

	if (font_size)
		param_set_font("measurefont", "* " + font_size.toString());
}

/* -- draw the parts and the tempo information -- */
// (unscaled delayed output)
function draw_partempo() {
    var	s, s2, some_part, some_tempo, h, w, y, st, p,
	sy = cur_sy

	// search the top staff
	for (st = 0; st <= nstaff; st++) {
		if (sy.st_print[st])
			break
	}
	if (st > nstaff)
		return				// no visible staff
	set_dscale(st, 1)			// no scale

	/* get the minimal y offset */
    var	ymin = staff_tb[st].topbar + 2,
		dosh = 0,
		shift = 1,
	x = -100		// (must be negative for %%soloffs)

	// output the parts
	for (s = tsfirst; s; s = s.ts_next) {
		s2 = s.part
		if (!s2 || s2.invis)
			continue
		if (!some_part) {
			some_part = s;
			set_font("parts");
			h = gene.curfont.size + 2 +
				gene.curfont.pad * 2
		}
		if (s2.x == undefined)
			s2.x = s.x - 10
		p = s2.text
		if (cfmt.partname)
			s2.ntxt = p = partname(p)[2]
		w = strwh(p)[0]
		y = y_get(st, true, s2.x, w + 3)
		if (ymin < y)
			ymin = y
	}
	if (some_part) {
		set_sscale(-1)
		ymin *= staff_tb[st].staffscale
		for (s = some_part; s; s = s.ts_next) {
			s2 = s.part
			if (!s2 || s2.invis)
				continue
			p = s2.ntxt || s2.text
			w = strwh(p)[0]
			if (user.anno_start || user.anno_stop) {
				s2.wl = 0
				s2.wr = w
				s2.ymn = ymin
				s2.ymx = s2.ymn + h
				anno_start(s2)
			}
			xy_str(s2.x,
				ymin + gene.curfont.pad + gene.curfont.size * .22,
				p)
			y_set(st, 1, s2.x, w + 3,
				(ymin + h) / staff_tb[st].staffscale)
			anno_stop(s2)
		}
	}

	// output the tempos
	ymin = staff_tb[st].topbar + 6
	for (s = tsfirst; s; s = s.ts_next) {
		if (s.type != C.TEMPO || s.invis)
			continue
		if (!some_tempo)
			some_tempo = s
		w = s.tempo_wh[0]
//		if (s.time == 0 && s.x > 40)	// at start of tune and no %%soloffs,
//			s.x = 40	// shift the tempo over the key signature
		y = y_get(st, true, s.x - 16, w)
		if (y > ymin)
			ymin = y
		if (x >= s.x - 16 && !(dosh & (shift >> 1)))
			dosh |= shift
		shift <<= 1
		x = s.x - 16 + w
	}
	if (some_tempo) {
		set_sscale(-1)
		set_font("tempo")
		ymin += 2
		ymin *= staff_tb[st].staffscale

		/* draw the tempo indications */
		for (s = some_tempo; s; s = s.ts_next) {
			if (s.type != C.TEMPO
			 || s.invis)		// (displayed by %%titleformat)
				continue
			w = s.tempo_wh[0]
			h = s.tempo_wh[1]
			y = ymin
			if (dosh & 1)
				y += h
			if (user.anno_start || user.anno_stop) {
				s.wl = 16
//				s.wr = 30
				s.wr = w - 16
				s.ymn = y
				s.ymx = s.ymn + 14
				anno_start(s)
			}
			writempo(s, s.x - 16, y)
			anno_stop(s)
			y_set(st, 1, s.x - 16, w,
				(y + h + 2) / staff_tb[st].staffscale)
			dosh >>= 1
		}
	}
}
// abc2svg - draw.js - draw functions
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// constants
var	STEM_MIN	= 16,	/* min stem height under beams */
	STEM_MIN2	= 14,	/* ... for notes with two beams */
	STEM_MIN3	= 12,	/* ... for notes with three beams */
	STEM_MIN4	= 10,	/* ... for notes with four beams */
	STEM_CH_MIN	= 14,	/* min stem height for chords under beams */
	STEM_CH_MIN2	= 10,	/* ... for notes with two beams */
	STEM_CH_MIN3	= 9,	/* ... for notes with three beams */
	STEM_CH_MIN4	= 9,	/* ... for notes with four beams */
	BEAM_DEPTH	= 3.2,	/* width of a beam stroke */
	BEAM_OFFSET	= .25,	/* pos of flat beam relative to staff line */
	BEAM_SHIFT	= 5,	/* shift of second and third beams */
	BEAM_STUB	= 7,	/* length of stub for flag under beam */ 
	SLUR_SLOPE	= .7,	// max slope of a slur
	GSTEM		= 15,	/* grace note stem length */
	GSTEM_XOFF	= 2.3	/* x offset for grace note stem */

    var cache,
	anno_a = []		// symbols with annotations

/* -- 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,
		depth = !grace ? BEAM_DEPTH : 1.8

	/* -- up/down shift needed to get k*6 -- */
	function rnd6(y) {
		var iy = Math.round((y + 12) / 6) * 6 - 12
		return iy - y
	} // rnd6()

	if (stem > 0) {
		bot = b - (nflags - 1) * shift - depth
		if (bot > 26)
			return 0
		top = b
	} else {
		top = b + (nflags - 1) * shift + depth
		if (top < -2)
			return 0
		bot = b
	}

	d1 = rnd6(top - BEAM_OFFSET);
	d2 = rnd6(bot + BEAM_OFFSET)
	return d1 * d1 > d2 * d2 ? d2 : d1
}

/* duplicate a note for beaming continuation */
function sym_dup(s) {
    var	m, note

	s = clone(s)
	s.invis = true
	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_dd
	}
	return s
}

/* -- calculate a beam -- */
/* (the staves may be defined or not) */
var min_tb = [
	[STEM_MIN, STEM_MIN,
		STEM_MIN2, STEM_MIN3, STEM_MIN4, STEM_MIN4],
	[STEM_CH_MIN, STEM_CH_MIN,
		STEM_CH_MIN2, STEM_CH_MIN3, STEM_CH_MIN4, STEM_CH_MIN4]
]

// (possible hook)
Abc.prototype.calculate_beam = function(bm, s1) {
    var	s, s2, g, notes, nflags, st, v, two_staves, two_dir,
	n, x, y, ys, a, b, stem_err, max_stem_err,
		p_min, p_max, s_closest,
		stem_xoff, scale,
		visible, dy

	if (!s1.beam_st) {	/* beam from previous music line */
		s = sym_dup(s1);
		lkvsym(s, s1);
		lktsym(s, s1);
		s.x -= 12
		if (s.x > s1.prev.x + 12)
			s.x = s1.prev.x + 12;
		s.beam_st = true
		delete s.beam_end;
		s.tmp = true
		delete s.sls;
		s1 = s
	}

	/* search last note in beam */
	notes = nflags = 0;	/* set x positions, count notes and flags */
	two_staves = two_dir = false;
	st = s1.st;
	v = s1.v;
	stem_xoff = s1.grace ? GSTEM_XOFF : 3.5
	for (s2 = s1;  ;s2 = s2.next) {
		if (s2.type == C.NOTE) {
			if (s2.nflags > nflags)
				nflags = s2.nflags;
			notes++
			if (s2.st != st)
				two_staves = true
			if (s2.stem != s1.stem)
				two_dir = true
			if (!visible && !s2.invis
			 && (!s2.stemless || s2.trem2))
				visible = true
			if (s2.beam_end)
				break
		}
		if (!s2.next) {		/* beam towards next music line */
			for (; ; s2 = s2.prev) {
				if (s2.type == C.NOTE)
					break
			}
			s = sym_dup(s2);
			s.next = s2.next
			if (s.next)
				s.next.prev = s;
			s2.next = s;
			s.prev = s2;
			s.ts_next = s2.ts_next
			if (s.ts_next)
				s.ts_next.ts_prev = s;
			s2.ts_next = s;
			s.ts_prev = s2
			delete s.beam_st;
			s.beam_end = true;
			s.tmp = true
			delete s.sls;
			s.x += 12
			if (s.x < realwidth - 12)
				s.x = realwidth - 12;
			s2 = s;
			notes++
			break
		}
	}

	// at least, must have a visible note with a stem
	if (!visible)
		return false;

	bm.s2 = s2			/* (don't display the flags) */

	if (staff_tb[st].y == 0) {	/* staves not defined */
		if (two_staves)
			return false
	} else {			/* staves defined */
//		if (!two_staves && !s1.grace) {
		if (!two_staves) {
			bm.s1 = s1;	/* beam already calculated */
			bm.a = (s1.ys - s2.ys) / (s1.xs - s2.xs);
			bm.b = s1.ys - s1.xs * bm.a + staff_tb[st].y;
			bm.nflags = nflags
			return true
		}
	}

	s_closest = s1;
	p_min = 100;
	p_max = 0
	for (s = s1; ; s = s.next) {
		if (s.type != C.NOTE)
			continue
		if ((scale = s.p_v.scale) == 1)
			scale = staff_tb[s.st].staffscale
		if (s.stem >= 0) {
			x = stem_xoff + s.notes[0].shhd
			if (s.notes[s.nhd].midi > p_max) {
				p_max = s.notes[s.nhd].midi
				s_closest = s
			}
		} else {
			x = -stem_xoff + s.notes[s.nhd].shhd
			if (s.notes[0].midi < p_min) {
				p_min = s.notes[0].midi
				s_closest = s
			}
		}
		s.xs = s.x + x * scale;
		if (s == s2)
			break
	}

	// have flat beams on grace notes when asked
	if (s.grace && s1.fmt.flatbeams)
		a = 0

	// if a note inside the beam is the closest to the beam, the beam is flat
	else if (!two_dir
	      && notes >= 3
	      && s_closest != s1 && s_closest != s2)
		a = 0

	y = s1.ys + staff_tb[st].y
	if (a == undefined) {
		if (two_dir
		 && s1.stem != s2.stem
		 && s1.st == s2.st) {		// if inverted stems,
			y -= 5 * s1.stem	// remove the beam depth
			s2.ys -= 5 * s2.stem			
		}
		a = (s2.ys + staff_tb[s2.st].y - y) / (s2.xs - s1.xs)
	}

	if (a != 0) {
		a = s1.fmt.beamslope * a /
			(s1.fmt.beamslope + Math.abs(a)) // max steepness for beam
		if (a > -.04 && a < .04)
			a = 0				// slope too low
	}

	// center the main beam
	b = (y + s2.ys + staff_tb[s2.st].y) / 2 - a * (s2.xs + s1.xs) / 2

	/* have room for the symbols in the staff */
	max_stem_err = 0;		/* check stem lengths */

	// when 2 directions, check if all beams are on the same side of the main beam
	s = s1
	if (two_dir) {
		n = 1
		while (1) {
			if (s.stem != s1.stem
			 && (s.nflags == 1
			  || s.beam_br1  || s.beam_br2)) {
				n = 0
				break
			}
			if (s == s2)
				break
			s = s.next
		}
		if (n)				// same side
			n = (s1.nflags + s2.nflags)
				* (s1.nflags >= s2.nflags ? s1.stem : s2.stem)
					/ 4
		else				// different sides
			n = -(s1.nflags * s1.stem + s2.nflags * s2.stem)
					/ 2
		b += ((s1.grace ? 3.5 : BEAM_SHIFT) * n
				+ BEAM_DEPTH * s1.stem) / 2
	} else if (!s1.grace) {		/* normal notes */
		var beam_h = BEAM_DEPTH + BEAM_SHIFT * (nflags - 1)
//--fixme: added for abc2svg
		while (s.ts_prev
		    && s.ts_prev.type == C.NOTE
		    && s.ts_prev.time == s.time
		    && s.ts_prev.x > s1.xs)
			s = s.ts_prev

		for (; s && s.time <= s2.time; s = s.ts_next) {
			if (s.type != C.NOTE
			 || s.invis
			 || (s.st != st
			  && s.v != v)) {
				continue
			}
			x = s.v == v ? s.xs : s.x;
			ys = a * x + b - staff_tb[s.st].y
			if (s.v == v) {
				stem_err = min_tb[s.nhd == 0 ? 0 : 1][s.nflags]
				if (s.stem > 0) {
					if (s.notes[s.nhd].pit > 26) {
						stem_err -= 2
						if (s.notes[s.nhd].pit > 28)
							stem_err -= 2
					}
					stem_err -= ys - 3 * (s.notes[s.nhd].pit - 18)
				} else {
					if (s.notes[0].pit < 18) {
						stem_err -= 2
						if (s.notes[0].pit < 16)
							stem_err -= 2
					}
					stem_err -= 3 * (s.notes[0].pit - 18) - ys
				}
				stem_err += BEAM_DEPTH + BEAM_SHIFT * (s.nflags - 1)
			} else {
/*fixme: KO when two_staves*/
				if (s1.stem > 0) {
					if (s.stem > 0) {
/*fixme: KO when the voice numbers are inverted*/
						if (s.ymn > ys + 4
						 || s.ymx < ys - beam_h - 2)
							continue
						if (s.v > v)
							stem_err = s.ymx - ys
						else
							stem_err = s.ymn + 8 - ys
					} else {
						stem_err = s.ymx - ys
					}
				} else {
					if (s.stem < 0) {
						if (s.ymx < ys - 4
						 || s.ymn > ys - beam_h - 2)
							continue
						if (s.v < v)
							stem_err = ys - s.ymn
						else
							stem_err = ys - s.ymx + 8
					} else {
						stem_err = ys - s.ymn
					}
				}
				stem_err += 2 + beam_h
			}
			if (stem_err > max_stem_err)
				max_stem_err = stem_err
		}
	} else {				/* grace notes */
		for ( ; ; s = s.next) {
			ys = a * s.xs + b - staff_tb[s.st].y;
			stem_err = GSTEM - 2
			if (s.stem > 0)
				stem_err -= ys - (3 * (s.notes[s.nhd].pit - 18))
			else
				stem_err += ys - (3 * (s.notes[0].pit - 18));
			stem_err += 3 * (s.nflags - 1)
			if (stem_err > max_stem_err)
				max_stem_err = stem_err
			if (s == s2)
				break
		}
	}

	if (max_stem_err > 0)		/* shift beam if stems too short */
		b += s1.stem * max_stem_err

	// have room for the gracenotes and clefs
    if (!two_staves && !two_dir)
	for (s = s1.next; ; s = s.next) {
		switch (s.type) {
		case C.REST:		/* cannot move rests in multi-voices */
			if (!s.multi)
				break
			g = s.ts_next
			if (!g || g.st != st
			 || (g.type != C.NOTE && g.type != C.REST))
				break
//fixme:too much vertical shift if some space above the note
//fixme:this does not fix rest under beam in second voice (ts_prev)
			/*fall thru*/
//		case C.BAR:
			if (s.invis)
				break
			/*fall thru*/
		case C.CLEF:
			y = a * s.x + b
			if (s1.stem > 0) {
				y = s.ymx - y
					+ BEAM_DEPTH + BEAM_SHIFT * (nflags - 1)
					+ 2
				if (y > 0)
					b += y
			} else {
				y = s.ymn - y
					- BEAM_DEPTH - BEAM_SHIFT * (nflags - 1)
					- 2
				if (y < 0)
					b += y
			}
			break
		case C.GRACE:
			for (g = s.extra; g; g = g.next) {
				y = a * g.x + b
				if (s1.stem > 0) {
					y = g.ymx - y
						+ BEAM_DEPTH + BEAM_SHIFT * (nflags - 1)
					if (y > 0)
						b += y
				} else {
					y = g.ymn - y
						- BEAM_DEPTH - BEAM_SHIFT * (nflags - 1)
					if (y < 0)
						b += y
				}
			}
			break
		}
		if (s == s2)
			break
	}

	if (a == 0)		/* shift flat beams onto staff lines */
		b += b_pos(s1.grace, s1.stem, nflags, b - staff_tb[st].y)

	/* adjust final stems and rests under beam */
	for (s = s1; ; s = s.next) {
		switch (s.type) {
		case C.NOTE:
			s.ys = a * s.xs + b - staff_tb[s.st].y
			if (s.stem > 0) {
				s.ymx = s.ys + 2.5
			} else {
				s.ymn = s.ys - 2.5
			}
			break
		case C.REST:
			y = a * s.x + b - staff_tb[s.st].y
			dy = BEAM_DEPTH + BEAM_SHIFT * (nflags - 1)
				+ (s.head != C.FULL ? 4 : 9)
			if (s1.stem > 0) {
				y -= dy
				if (s1.multi == 0 && y > 12)
					y = 12
				if (s.y <= y)
					break
			} else {
				y += dy
				if (s1.multi == 0 && y < 12)
					y = 12
				if (s.y >= y)
					break
			}
			if (s.head != C.FULL)
				y = (((y + 3 + 12) / 6) | 0) * 6 - 12;
			s.y = y
			break
		}
		if (s == s2)
			break
	}

	/* save beam parameters */
	if (staff_tb[st].y == 0)	/* if staves not defined */
		return false
	bm.s1 = s1;
	bm.a = a;
	bm.b = b;
	bm.nflags = nflags
	return true
}

/* -- draw the beams for one word -- */
/* (the staves are defined) */
function draw_beams(bm) {
    var	s, i, beam_dir, shift, bshift, bstub, bh, da, bsh,
		k, k1, k2, x1,
	osh = 0,				// shift other side
		s1 = bm.s1,
		s2 = bm.s2

	/* -- draw a single beam -- */
	function draw_beam(x1, x2, dy, h, bm,
				 n) {		/* beam number (1..n) */
		var	y1, dy2,
			s = bm.s1,
			nflags = s.nflags

		if (s.ntrem)
			nflags -= s.ntrem
		if (s.trem2 && n > nflags) {
			if (s.dur >= C.BLEN / 2) {
				x1 = s.x + 6;
				x2 = bm.s2.x - 6
			} else if (s.dur < C.BLEN / 4) {
			    var	dx = x2 - x1
				if (dx < 16) {
					x1 += dx / 4
					x2 -= dx / 4
				} else {
					x1 += 5
					x2 -= 6
				}
			}
		}

		y1 = bm.a * x1 + bm.b - dy;
		x2 -= x1;
		x2 /= stv_g.scale;
		dy2 = bm.a * x2 * stv_g.stsc
		xypath(x1, y1, true);
		output += 'l' + x2.toFixed(1) + ' ' + (-dy2).toFixed(1) +
			'v' + h.toFixed(1) +
			'l' + (-x2).toFixed(1) + ' ' + dy2.toFixed(1) +
			'z"/>\n'
	} // draw_beam()

	anno_start(s1, 'beam')
/*fixme: KO if many staves with different scales*/
//	set_scale(s1)
	if (!s1.grace) {
		bshift = BEAM_SHIFT;
		bstub = BEAM_STUB;
		shift = .34;		/* (half width of the stem) */
		bh = BEAM_DEPTH
	} else {
		bshift = 3.5;
		bstub = 3.2;
		shift = .29;
		bh = 1.8
	}
	bh /= stv_g.scale

/*fixme: quick hack for stubs at end of beam and different stem directions*/
	beam_dir = s1.stem
	if (s1.stem != s2.stem
	 && s1.nflags > s2.nflags)
		beam_dir = s2.stem
	if (beam_dir < 0)
		bh = -bh;

	/* make first beam over whole word and adjust the stem lengths */
	draw_beam(s1.xs - shift, s2.xs + shift, 0, bh, bm, 1);
	da = 0
	for (s = s1; ; s = s.next) {
		if (s.type == C.NOTE
		 && s.stem != beam_dir)
			s.ys = bm.a * s.xs + bm.b
				- staff_tb[s.st].y
				+ bshift * (s.nflags - 1) * s.stem
				- bh
		if (s == s2)
			break
	}

	if (s1.feathered_beam) {
		da = bshift / (s2.xs - s1.xs)
		if (s1.feathered_beam > 0) {
			da = -da;
			bshift = da * s1.xs
		} else {
			bshift = da * s2.xs
		}
		da = da * beam_dir
	}

	/* other beams with two or more flags */
	shift = 0
	for (i = 2; i <= bm.nflags; i++) {
		shift += bshift
		if (da != 0)
			bm.a += da
		for (s = s1; ; s = s.next) {
			if (s.type != C.NOTE
			 || s.nflags < i) {
				if (s == s2)
					break
				continue
			}
			if (s.trem1
			 && i > s.nflags - s.ntrem) {
				x1 = (s.dur >= C.BLEN / 2) ? s.x : s.xs;
				draw_beam(x1 - 5, x1 + 5,
					  (shift + 2.5) * beam_dir,
					  bh, bm, i)
				if (s == s2)
					break
				continue
			}
			k1 = s
			while (1) {
				if (s == s2)
					break
				k = s.next
				if (k.type == C.NOTE || k.type == C.REST) {
					if (k.trem1){
						if (k.nflags - k.ntrem < i)
							break
					} else if (k.nflags < i) {
						break
					}
				}
				if (k.beam_br1
				 || (k.beam_br2 && i > 2))
					break
				s = k
			}
			k2 = s
			while (k2.type != C.NOTE)
				k2 = k2.prev;
			x1 = k1.xs
			bsh = shift * beam_dir
			if (k1 == k2) {
				if (k1 == s1) {
					x1 += bstub
				} else if (k1 == s2) {
					x1 -= bstub
				} else if (k1.beam_br1
				        || (k1.beam_br2
					 && i > 2)) {
					x1 += bstub
				} else {
					k = k1.next
					while (k.type != C.NOTE)
						k = k.next
					if (k.beam_br1
					 || (k.beam_br2 && i > 2)) {
						x1 -= bstub
					} else {
						k1 = k1.prev
						while (k1.type != C.NOTE)
							k1 = k1.prev
						if (k1.nflags < k.nflags
						 || (k1.nflags == k.nflags
						  && k1.dots < k.dots))
							x1 += bstub
						else
							x1 -= bstub
					}
				}
				if (k1.stem != beam_dir) {
					osh -= bshift
					bsh = osh * beam_dir
					k1.ys = bm.a * k1.xs + bm.b
						- staff_tb[k1.st].y - bh
				}
			} else if (k1.stem == k2.stem && k1.stem != beam_dir) {

				// inverted stems: put the beam on the other side
				osh -= bshift
				bsh = osh * beam_dir
				for (s = k1; ; s = s.next) {
					if (s.type == C.NOTE)
						s.ys = bm.a * s.xs + bm.b
							- staff_tb[s.st].y
							- bh
					if (s == k2)
						break
				}
			}
			draw_beam(x1, k2.xs,
				  bsh,
				  bh, bm, i)
			if (s == s2)
				break
		}
	}
	if (s1.tmp)
		unlksym(s1)
	else if (s2.tmp)
		unlksym(s2)
	anno_stop(s1, 'beam')
}

/* -- draw the left side of the staves -- */
function draw_lstaff(x) {
    var	i, j, yb, h, fl,
		nst = cur_sy.nstaff,
		l = 0

	/* -- draw a system brace or bracket -- */
	function draw_sysbra(x, st, flag) {
		var i, st_end, yt, yb

		while (!cur_sy.st_print[st]) {
			if (cur_sy.staves[st].flags & flag)
				return
			st++
		}
		i = st_end = st
		while (1) {
			if (cur_sy.st_print[i])
				st_end = i
			if (cur_sy.staves[i].flags & flag)
				break
			i++
		}
		yt = staff_tb[st].y + staff_tb[st].topbar
					* staff_tb[st].staffscale;
		yb = staff_tb[st_end].y + staff_tb[st_end].botbar
					* staff_tb[st_end].staffscale
		if (flag & (CLOSE_BRACE | CLOSE_BRACE2))
			out_brace(x, yb, yt - yb)
		else
			out_bracket(x, yt, yt - yb)
	}

	for (i = 0; ; i++) {
		fl = cur_sy.staves[i].flags
		if (fl & (OPEN_BRACE | OPEN_BRACKET))
			l++
		if (cur_sy.st_print[i])
			break
		if (fl & (CLOSE_BRACE | CLOSE_BRACKET))
			l--
		if (i == nst)
			break
	}
	for (j = nst; j > i; j--) {
		if (cur_sy.st_print[j])
			break
	}
	if (i == j && l == 0)
		return
	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++) {
		fl = cur_sy.staves[i].flags
		if (fl & OPEN_BRACE)
			draw_sysbra(x, i, CLOSE_BRACE)
		if (fl & OPEN_BRACKET)
			draw_sysbra(x, i, CLOSE_BRACKET)
		if (fl & OPEN_BRACE2)
			draw_sysbra(x - 6, i, CLOSE_BRACE2)
		if (fl & OPEN_BRACKET2)
			draw_sysbra(x - 6, i, CLOSE_BRACKET2)
	}
}

/* -- draw the time signature -- */
function draw_meter(s) {
	if (!s.a_meter)
		return
    var	i, m, meter, x, x0, yt,
	p_staff = staff_tb[s.st],
	y = p_staff.y

	// adjust the vertical offset according to the staff definition
	if (p_staff.stafflines != '|||||')
		y += (p_staff.topbar + p_staff.botbar) / 2 - 12	// bottom

	for (i = 0; i < s.a_meter.length; i++) {
		meter = s.a_meter[i];
		x = s.x + s.x_meter[i]
		yt = y + (meter.bot ? 18 : 12)
		if (s.a_meter[i + 1]
		 && (s.a_meter[i + 1].top == '|'
		  || s.a_meter[i + 1].top == '.')) {
			xygl(x, yt, "mtr" + meter.top[0] + s.a_meter[i + 1].top)
			i++
			continue
		}
		xygl(x, yt, "mtr" + meter.top[0])
		if (meter.top.length > 1) {
			m = 0
			x0 = x
			while (1) {
				switch (meter.top[m]) {
				case '(':
				case ')':
					x += 4
					break
				case '1':
					x += 8
					break
				case ' ':
					x += 4
					break
				case '+':
					x += 2
					// fall thru
				default:
					x += 10
					break
				}
				if (++m >= meter.top.length)
					break
				xygl(x, yt, "mtr" + meter.top[m])
			}
			x = (x0 + x) / 2 - 5
		}
		if (meter.bot) {
			if (meter.bot[1]) {
				if (meter.bot[0] == 1) {
					x0 = x - 4
					x += 4
				} else {
					x0 = x - 5
					x += 5
				}
				xygl(x0, y + 6, "mtr" + meter.bot[0])
				xygl(x, y + 6, "mtr" + meter.bot[1])
			} else {
				xygl(x, y + 6, "mtr" + meter.bot[0])
			}
		}
	}
}

    var	acc_nd = {}		// cache of the microtonal accidentals

/* -- draw an accidental -- */
function draw_acc(x, y, a) {
	if (typeof a == "object") {		// 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
// (possible hook)
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
	 || s.invis)
		return			// no helper line (no line)
	for (i = 0; i <= s.nhd; i++) {
		note = s.notes[i]
		if (!p_staff.hlmap[note.pit - p_staff.hll])
			hla.push([note.pit - 18,
				  note.shhd * s.p_v.scale])
	}
	n = hla.length
	if (!n)
		return			// no

	// handle the helper lines out of the staff
    var	dx1, dx2, hl, shhd,hlp,
	stafflines = cur_sy.staves[st].stafflines,
	top = stafflines.length - 1,
	yu =  top,
	bot = (p_staff.hll - 17) / 2,
	yl = bot,
	dx = (s.grace ? 4 : hw_tb[s.head] * 1.3) * s.p_v.scale

	// 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++) {
		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 (hlp > top * 2) {
			yu = hlp >> 1
			n--
		}
		set_hl(p_staff, hlp >> 1, s.x, dx1, dx2)
	}

	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] == '-')
		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)
var	sharp_cl = new Int8Array([24, 9, 15, 21, 6, 12, 18]),
	flat_cl = new Int8Array([12, 18, 24, 9, 15, 21, 6]),
	sharp1 = new Int8Array([-9, 12, -9, -9, 12, -9]),
	sharp2 = new Int8Array([12, -9, 12, -9, 12, -9]),
	flat1 = new Int8Array([9, -12, 9, -12, 9, -12]),
	flat2 = new Int8Array([-12, 9, -12, 9, -12, 9])

Abc.prototype.draw_keysig = function(x, s) {
	var	old_sf = s.k_old_sf,
		st = s.st,
		staffb = staff_tb[st].y,
		i, shift, p_seq,
		clef_ix = s.k_y_clef,
	a_acc = s.k_a_acc			// accidental list [pit, acc]

	// set the accidentals when K: with modified accidentals
	function set_k_acc(a_acc, sf) {
	    var i, j, n, nacc, p_acc,
		accs = [],
		pits = []

		if (sf > 0) {
			for (nacc = 0; nacc < sf; nacc++) {
				accs[nacc] = 1			// sharp
				pits[nacc] = [26, 23, 27, 24, 21, 25, 22][nacc]
			}
		} else {
			for (nacc = 0; nacc < -sf; nacc++) {
				accs[nacc] = -1			// flat
				pits[nacc] = [22, 25, 21, 24, 20, 23, 26][nacc]
			}
		}
		n = a_acc.length
		for (i = 0; i < n; i++) {
			p_acc = a_acc[i]
			for (j = 0; j < nacc; j++) {
				if (pits[j] == p_acc.pit) {
					accs[j] = p_acc.acc
					break
				}
			}
			if (j == nacc) {
				accs[j] = p_acc.acc
				pits[j] = p_acc.pit
				nacc++
			}
		}
		for (i = 0; i < nacc; i++) {
			p_acc = a_acc[i]
			if (!p_acc)
				p_acc = a_acc[i] = {}
			p_acc.acc = accs[i]
			p_acc.pit = pits[i]
		}
	} // set_k_acc()

	// ---- draw_keysig ---
	if (clef_ix & 1)
		clef_ix += 7;
	clef_ix /= 2
	while (clef_ix < 0)
		clef_ix += 7;
	clef_ix %= 7

	/* normal accidentals */
	if (a_acc && !s.exp)			// if added accidentals
		set_k_acc(a_acc, s.k_sf)	// merge them into the key

	if (!a_acc) {

		/* put neutrals if 'accidental cancel' */
		if (s.fmt.cancelkey || s.k_sf == 0) {

			/* when flats to sharps, or sharps to flats, */
			if (s.k_sf == 0
			 || old_sf * s.k_sf < 0) {

				/* old sharps */
				shift = sharp_cl[clef_ix];
				p_seq = shift > 9 ? sharp1 : sharp2
				for (i = 0; i < old_sf; i++) {
					xygl(x, staffb + shift, "acc3");
					shift += p_seq[i];
					x += 5.5
				}

				/* old flats */
				shift = flat_cl[clef_ix];
				p_seq = shift < 18 ? flat1 : flat2
				for (i = 0; i > old_sf; i--) {
					xygl(x, staffb + shift, "acc3");
					shift += p_seq[-i];
					x += 5.5
				}
				if (s.k_sf != 0)
					x += 3		/* extra space */
			}
		}

		/* new sharps */
		if (s.k_sf > 0) {
			shift = sharp_cl[clef_ix];
			p_seq = shift > 9 ? sharp1 : sharp2
			for (i = 0; i < s.k_sf; i++) {
				xygl(x, staffb + shift, "acc1");
				shift += p_seq[i];
				x += 5.5
			}
			if (s.fmt.cancelkey && i < old_sf) {
				x += 2
				for (; i < old_sf; i++) {
					xygl(x, staffb + shift, "acc3");
					shift += p_seq[i];
					x += 5.5
				}
			}
			if (s.k_bagpipe == 'p') {	// K:Hp - add the g natural
				xygl(x, staffb + 27, "acc3")
				x += 5.5
			}
		}

		/* new flats */
		if (s.k_sf < 0) {
			shift = flat_cl[clef_ix];
			p_seq = shift < 18 ? flat1 : flat2
			for (i = 0; i > s.k_sf; i--) {
				xygl(x, staffb + shift, "acc-1");
				shift += p_seq[-i];
				x += 5.5
			}
			if (s.fmt.cancelkey && i > old_sf) {
				x += 2
				for (; i > old_sf; i--) {
					xygl(x, staffb + shift, "acc3");
					shift += p_seq[-i];
					x += 5.5
				}
			}
		}
	} else if (a_acc.length) {

		/* explicit accidentals */
		var	acc,
			last_acc = a_acc[0].acc,
			last_shift = 100,
			s2 = {
				st: st,
				nhd: 0,
				notes: [{}]
			}

		for (i = 0; i < a_acc.length; i++) {
			acc = a_acc[i];
			shift = (s.k_y_clef	// clef shift
				+ acc.pit - 18) * 3
			while (shift < -3)		// let the accidentals inside the staff
				shift += 21
			while (shift > 27)
				shift -= 21
			if (i != 0
			 && (shift > last_shift + 18
			  || shift < last_shift - 18))
				x -= 5.5		// no clash
			else if (acc.acc != last_acc)
				x += 3;
			last_acc = acc.acc;
			s2.x = x
			s2.notes[0].pit = shift / 3 + 18;
// is this useful?
//			s2.head = C.FULL
//			s2.notes[0].shhd = 0
//			self.draw_hl(s2)
			last_shift = shift;
			draw_acc(x, staffb + shift, acc.acc)
			x += 5.5
		}
	}
}

// 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()

// if rest alone in the measure or measure repeat,
// change the head and center
function center_rest(s) {
    var	s2, x
	
	if (s.dur < C.BLEN * 2)
		s.nflags = -2		// semibreve / whole
	else if (s.dur < C.BLEN * 4)
		s.nflags = -3
	else
		s.nflags = -4
	s.dots = 0

	/* don't use next/prev: there is no bar in voice overlay */
	s2 = s.ts_next
	while (s2.time != s.time + s.dur
	    && s2.ts_next)
		s2 = s2.ts_next
	x = s2.x - s2.wl
	s2 = s
	while (!s2.seqst)
		s2 = s2.ts_prev
	s2 = s2.ts_prev
	x = (x + s2.x + s2.wr) / 2

	/* center the associated decorations */
	if (s.a_dd)
		deco_update(s, x - s.x)
	s.x = x
} // center_rest()

/* -- draw a rest -- */
/* (the staves are defined) */
var rest_tb = [
	"r128", "r64", "r32", "r16", "r8",
	"r4",
	"r2", "r1", "r0", "r00"]

function draw_rest(s) {
    var	s2, i, j, y, bx,
	p_staff = staff_tb[s.st],
	yb = p_staff.y,			// bottom of staff
	x = s.x

	if (s.notes[0].shhd)
		x += s.notes[0].shhd * stv_g.scale

	if (s.rep_nb) {
		set_sscale(s.st);
		anno_start(s);
		if (p_staff.stafflines == '|||||')
			yb += 12
		else
			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
			 && s.fmt.measrepnb > 0
			 && !(s.rep_nb % s.fmt.measrepnb))
				nrep_out(x, yb + p_staff.topbar, s.rep_nb)
		}
		anno_a.push(s)
		return
	}

	set_scale(s);
	anno_start(s);

	if (s.notes[0].color)
		set_color(s.notes[0].color);

	y = s.y;

	i = 5 - s.nflags		/* rest_tb index (5 = C_XFLAGS) */
	if (i == 7 && y == 12
	 && p_staff.stafflines.length <= 2)
		y -= 6				/* semibreve a bit lower */

	// draw the rest
	if (!s.notes[0].invis)		// if not head replacement
		xygl(x, y + yb, rest_tb[i])

	if (s.dots) {
		x += 8;
		y += yb + 3
		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_a.push(s)
}

// -- draw a multi-measure rest --
// (the staves are defined)
function draw_mrest(s) {
    var	x1, x2, s2,
	p_st = staff_tb[s.st],
	y = p_st.y + (p_st.topbar + p_st.botbar) / 2,
	p = s.nmes.toString()

	// output an old multimeasure rest
	function omrest() {
	    var	x = s.x,
		y = p_st.y + 12,
		n = s.nmes,
		k = n >> 2			// number of rests

		if (n & 3) {
			k++
			if (n & 3 == 3)
				k++
		}
//dx = 6
		x -= 3 * (k - 1)
		while (n >= 4) {
			xygl(x, y, "r00")
			n -= 4
			x += 6
		}
		if (n >= 2) {
			xygl(x, y, "r0")
			n -= 2
			x += 6
		}
		if (n)
			xygl(x + 2, y, "r1")
	} // omrest()

	set_scale(s)

	s2 = s			// search the start of the previous time sequence
	while (!s2.seqst)
		s2 = s2.ts_prev
	s2 = s2.ts_prev
	while (!s2.seqst)
		s2 = s2.ts_prev
	x1 = s2.x + 20

	s2 = s.ts_next		// search the next symbol on the same staff
	if (s2.staff != s.staff)
		s2 = s.next
	x2 = s2.x - 20

	s.x = (x1 + x2) / 2
	anno_start(s)
	if (!cfmt.oldmrest || s.nmes > cfmt.oldmrest) {
		out_XYAB('<path d="mX Y', x1 + .6, y - 2.7)
		output += 'v2.7h-1.4v-10.8h1.4v2.7h'
			+ ((x2 - x1 - 2.8) / stv_g.scale).toFixed(1)
			+ 'v-2.7h1.4v10.8h-1.4v-2.7z"/>\n'
	} else {
		omrest()		// old multirest
	}
    if (s.tacet)
	out_XYAB('<text x ="X" y="Y" style="font-size:12px;font-weight:700"\
 text-anchor="middle">A</text>\n',
		s.x, y + 18, s.tacet)
    else
	out_XYAB('<text x ="X" y="Y" text-anchor="middle">A</text>\n',
		s.x, y + 22, m_gl(p))
	anno_a.push(s)
} // draw_mrest()

function grace_slur(s) {
    var	yy, x0, y0, x3, y3, bet1, bet2, dy1, dy2, last, below,
	so = s,
	g = s.extra

	while (1) {
		if (!g.next)
			break			/* (keep the last note) */
		g = g.next
	}
	last = g

	below = ((g.stem >= 0 || s.multi < 0) && g.notes[0].pit <= 28)
			|| g.notes[0].pit < 16
	if (below) {
		yy = 127
		for (g = s.extra; g; g = g.next) {
			if (g.y < yy) {
				yy = g.y;
				last = g
			}
		}
		x0 = last.x;
		y0 = last.y - 5
		if (s.extra != last) {
			x0 -= 4;
			y0 += 1
		}
		s = s.next;
		x3 = s.x - 1
		if (s.stem < 0 && s.nflags > -2)
			x3 -= 4;
		y3 = 3 * (s.notes[0].pit - 18) - 5;
		dy1 = (x3 - x0) * .4
		if (dy1 > 3)
			dy1 = 3;
		dy2 = dy1;
		bet1 = .2;
		bet2 = .8
		if (y0 > y3 + 7) {
			x0 = last.x - 1;
			y0 += .5;
			y3 += 6.5;
			x3 = s.x - 5.5;
			dy1 = (y0 - y3) * .8;
			dy2 = (y0 - y3) * .2;
			bet1 = 0
		} else if (y3 > y0 + 4) {
			y3 = y0 + 4;
			x0 = last.x + 2;
			y0 = last.y - 4
		}
	} else {				// slur above
		yy = -127
		for (g = s.extra; g; g = g.next) {
			if (g.y > yy) {
				yy = g.y;
				last = g
			}
		}
		x0 = last.x;
		y0 = last.y + 5
		if (s.extra != last) {
			x0 -= 4;
			y0 -= 1
		}
		s = s.next;
		x3 = s.x - 1
		if (s.stem >= 0 && s.nflags > -2)
			x3 -= 2;
		y3 = 3 * (s.notes[s.nhd].pit - 18) + 5;
		dy1 = (x0 - x3) * .4
		if (dy1 < -3)
			dy1 = -3;
		dy2 = dy1;
		bet1 = .2;
		bet2 = .8
		if (y0 < y3 - 7) {
			x0 = last.x - 1;
			y0 -= .5;
			y3 -= 6.5;
			x3 = s.x - 5.5;
			dy1 = (y0 - y3) * .8;
			dy2 = (y0 - y3) * .2;
			bet1 = 0
		} else if (y3 < y0 - 4) {
			y3 = y0 - 4;
			x0 = last.x + 2;
			y0 = last.y + 4
		}
	}

	so.slur = {
		x0: x0,
		y0: y0,
		x1: bet1 * x3 + (1 - bet1) * x0 - x0,
		y1: y0 - bet1 * y3 - (1 - bet1) * y0 + dy1,
		x2: bet2 * x3 + (1 - bet2) * x0 - x0,
		y2: y0 - bet2 * y3 - (1 - bet2) * y0 + dy2,
		x3: x3 - x0,
		y3: y0 - y3
	}
	y0 -= so.slur.y1
	g = so.extra
//fixme: useless?
//	y_set(s.st, !below, x0, x3 - x0, y0)
	if (below) {
		if (y0 < g.ymn)
			g.ymn = y0
	} else {
		if (y0 > g.ymx)
			g.ymx = y0
	}
} // grace_slur()

/* -- draw grace notes -- */
/* (the staves are defined) */
function draw_gracenotes(s) {
    var	x1, y1,
	last, note,
	bm = {},
	g = s.extra

	/* draw the notes */
//	bm.s2 = undefined			/* (draw flags) */
	while (1) {
		if (g.beam_st && !g.beam_end) {
			if (self.calculate_beam(bm, g))
				draw_beams(bm)
		}
		anno_start(g)
		draw_note(g, !bm.s2)
		if (g == bm.s2)
			bm.s2 = null			/* (draw flags again) */
		anno_a.push(s)
//		if (g.sls || g.sl2)
//			slur = true
		if (!g.next)
			break			/* (keep the last note) */
		g = g.next
	}
	last = g

	// if an acciaccatura, draw a bar 
	if (s.sappo) {
		g = s.extra
		if (!g.next) {			/* if one note */
			x1 = 9
			y1 = g.stem > 0 ? 5 : -5
		} else {			/* many notes */
			x1 = (g.next.x - g.x) * .5 + 4
			y1 = (g.ys + g.next.ys) * .5 - g.y
			if (g.stem > 0)
				y1 -= 1
			else
				y1 += 1
		}
		note = g.notes[g.stem < 0 ? 0 : g.nhd]
		out_acciac(g.x, y_head(g, note),
				x1, y1, g.stem > 0)
	}

	/* slur */
	g = s.slur
	if (g) {
		anno_start(s, 'slur')
		xypath(g.x0, g.y0 + staff_tb[s.st].y)
		output += 'c' + g.x1.toFixed(1) + ' ' + g.y1.toFixed(1) +
			' ' + g.x2.toFixed(1) + ' ' + g.y2.toFixed(1) +
			' ' + g.x3.toFixed(1) + ' ' + g.y3.toFixed(1) + '"/>\n'
		anno_stop(s, 'slur')
	}
}

/* -- set the y offset of the dots -- */
function setdoty(s, y_tb) {
	var m, m1, y

	/* set the normal offsets */
	for (m = 0; m <= s.nhd; m++) {
		y = 3 * (s.notes[m].pit - 18)	/* note height on staff */
		if ((y % 6) == 0) {
			if (s.dot_low)
				y -= 3
			else
				y += 3
		}
		y_tb[m] = y
	}
	/* dispatch and recenter the dots in the staff spaces */
	for (m = 0; m < s.nhd; m++) {
		if (y_tb[m + 1] > y_tb[m])
			continue
		m1 = m
		while (m1 > 0) {
			if (y_tb[m1] > y_tb[m1 - 1] + 6)
				break
			m1--
		}
		if (3 * (s.notes[m1].pit - 18) - y_tb[m1]
				< y_tb[m + 1] - 3 * (s.notes[m + 1].pit - 18)) {
			while (m1 <= m)
				y_tb[m1++] -= 6
		} else {
			y_tb[m + 1] = y_tb[m] + 6
		}
	}
}

// get the y offset of a note head
// (when the staves are defined)
function y_head(s, note) {
	return staff_tb[s.st].y + 3 * (note.pit - 18)
}

/* -- draw m-th head with accidentals and dots -- */
/* (the staves are defined) */
// sets {x,y}_note
function draw_basic_note(s, m, y_tb) {
    var	i, p, yy, dotx, doty, inv, head, dots, nflags,
		old_color = false,
		note = s.notes[m],
		staffb = staff_tb[s.st].y,	/* bottom of staff */
	x = s.x,
		y = 3 * (note.pit - 18),	/* note height on staff */
		shhd = note.shhd * stv_g.scale,
		x_note = x + shhd,
		y_note = y + staffb

//	/* special case for voice unison */
//	if (s.nohdi1 != undefined
//	 && m >= s.nohdi1 && m < s.nohdi2)
//		return

	if (note.dur == s.dur) {
		head = s.head
		dots = s.dots
		nflags = s.nflags
	} else {
		i = identify_note(s, note.dur)
		head = i[0]
		dots = i[1]
		nflags = i[2]
	}

	/* draw the head */
	if (note.invis) {
		;
	} else if (note.map && note.map[0]) {
		i = head;
		p = note.map[0][i]		// heads
		if (!p)
			p = note.map[0][note.map[0].length - 1]
		i = p.indexOf('/')
		if (i >= 0) {			// stem dependant
			if (s.stem >= 0)
				p = p.slice(0, i)
			else
				p = p.slice(i + 1)
		}
	} else if (s.type == C.CUSTOS) {
		p = "custos"
	} else {
		switch (head) {
		case C.OVAL:
			p = "HD"
			break
		case C.OVALBARS:
			if (s.head != C.SQUARE) {
				p = "HDD"
				break
			}
			// fall thru
		case C.SQUARE:
			if (nflags > -4) {
				p = "breve"
			} else {
				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
			p = "hd"
			break
		}
	}
	if (note.color != undefined)
		old_color = set_color(note.color)
	if (p) {
		if (s.grace || inv) {
			if (s.grace)
				g_open(x_note, y_note, 0, .66, 0)
			else
				g_open(x_note, y_note, 0, 1, -1)
			x_note = y_note = 0
		}
		if (!self.psxygl(x_note, y_note, p))
			xygl(x_note, y_note, p)
		if (s.grace || inv)
			g_close()
	}

	/* draw the dots */
/*fixme: to see for grace notes*/
	// (s.dots may be removed in tablatures - see strtab)
	if (dots && (s.dots || note.dur != s.dur)) {
		dotx = x + (6.6 + s.xmx) * stv_g.scale
		if (y_tb[m] == undefined) {
			y_tb[m] = 3 * (s.notes[m].pit - 18)
			if ((s.notes[m].pit & 1) == 0)
				y_tb[m] += 3
		}
		doty = y_tb[m] + staffb
		i = (note.dur / 12) >> ((5 - nflags) - dots)
		while (dots-- > 0) {
			xygl(dotx, doty, (i & (1 << dots)) ? "dot" : "dot+")
			dotx += 3.5
		}
	}

	/* draw the accidental */
	if (note.acc) {
		x -= note.shac * stv_g.scale
		if (!s.grace) {
			draw_acc(x, y + staffb, note.acc)
		} else {
			g_open(x, y + staffb, 0, .75);
			draw_acc(0, 0, note.acc)
			g_close()
		}
	}
	if (old_color != false)
		set_color(old_color)
}

/* -- draw a note or a chord -- */
/* (the staves are defined) */
function draw_note(s,
		   fl) {		// draw flags
    var	s2, i, m, y, slen, c, nflags,
	y_tb = new Array(s.nhd + 1),
	note = s.notes[s.stem < 0 ? s.nhd : 0],	// master note head
	x = s.x,
	x_st = s.x + note.shhd * stv_g.scale,
	y = y_head(s, note),
	staffb = staff_tb[s.st].y

	if (s.dots)
		setdoty(s, y_tb)

	/* draw the stem and flags */
	if (!s.stemless) {
		slen = s.ys - s.y;
		nflags = s.nflags
		if (s.ntrem)
			nflags -= s.ntrem
		if (!fl || nflags <= 0) {	/* stem only */
			if (s.nflags > 0) {	/* (fix for PS low resolution) */
				if (s.stem >= 0)
					slen -= 1
				else
					slen += 1
			}
			out_stem(x_st, y, slen, s.grace)
		} else {				/* stem and flags */
			out_stem(x_st, y, slen, s.grace,
				 nflags, s.fmt.straightflags)
		}
	} else if (s.xstem) {				/* cross-staff stem */
		s2 = s.ts_prev;
		slen = (s2.stem > 0 ? s2.y : s2.ys) - s.y;
		slen += staff_tb[s2.st].y - staffb;
		out_stem(x_st, y, slen)
	}

	/* draw the tremolo bars */
	if (fl && s.trem1) {
		var	ntrem = s.ntrem || 0,
			x1 = x;
		slen = 3 * (s.notes[s.stem > 0 ? s.nhd : 0].pit - 18)
		if (s.head == C.FULL || s.head == C.EMPTY) {
			x1 += (s.grace ? GSTEM_XOFF : 3.5) * s.stem
			if (s.stem > 0)
				slen += 6 + 5.4 * ntrem
			else
				slen -= 6 + 5.4
		} else {
			if (s.stem > 0)
				slen += 5 + 5.4 * ntrem
			else
				slen -= 5 + 5.4
		}
		slen /= s.p_v.scale;
		out_trem(x1, staffb + slen, ntrem)
	}

	/* draw the note heads */
	for (m = 0; m <= s.nhd; m++)
		draw_basic_note(s, m, y_tb)
}

// find where to start a long decoration
function prev_scut(s) {
	while (s.prev) {
		s = s.prev
		if (s.rbstart)
			return s
	}

	/* return a symbol of any voice starting before the start of the voice */
	s = s.p_v.sym
	while (s.type != C.CLEF)
		s = s.ts_prev		/* search a main voice */
	if (s.next && s.next.type == C.KEY)
		s = s.next
	if (s.next && s.next.type == C.METER)
		return s.next
	return s
}

/* -- decide whether a slur goes up or down (same voice) -- */
function slur_direction(k1, k2) {
    var	s, some_upstem, low, dir

	// check if slur sequence in a multi-voice staff
	function slur_multi(s1, s2) {
//		while (1) {
//			if (s1.multi)		// if multi voice
//				//fixme: may change
//				return s1.multi
//			if (s1 == s2)
//				break
//			s1 = s1.next
//		}
		if (s1.multi)
			return s1.multi
		if (s2.multi)
			return s2.multi
		return 0
	} // slur_multi()

	if (k1.grace && k1.stem > 0)
		return -1

	dir = slur_multi(k1, k2)
	if (dir)
		return dir

	for (s = k1; ; s = s.next) {
		if (s.type == C.NOTE) {
			if (!s.stemless) {
				if (s.stem < 0)
					return 1
				some_upstem = true
			}
			if (s.notes[0].pit < 22)	/* if under middle staff */
				low = true
		}
//		if (s == k2)
		if (s.time == k2.time)		// (k2 may be a grace note)
			break
	}
	if (!some_upstem && !low)
		return 1
	return -1
}

/* -- output a slur / tie -- */
function slur_out(x1, y1, x2, y2, dir, height, dotted) {
	var	dx, dy, dz,
		alfa = .3,
		beta = .45;

	/* for wide flat slurs, make shape more square */
	dy = y2 - y1
	if (dy < 0)
		dy = -dy;
	dx = x2 - x1
	if (dx > 40. && dy / dx < .7) {
		alfa = .3 + .002 * (dx - 40.)
		if (alfa > .7)
			alfa = .7
	}

	/* alfa, beta, and height determine Bezier control points pp1,pp2
	 *
	 *           X====alfa===|===alfa=====X
	 *	    /		 |	       \
	 *	  pp1		 |	        pp2
	 *	  /	       height		 \
	 *	beta		 |		 beta
	 *      /		 |		   \
	 *    p1		 m		     p2
	 *
	 */

	var	mx = .5 * (x1 + x2),
		my = .5 * (y1 + y2),
		xx1 = mx + alfa * (x1 - mx),
		yy1 = my + alfa * (y1 - my) + height;
	xx1 = x1 + beta * (xx1 - x1);
	yy1 = y1 + beta * (yy1 - y1)

	var	xx2 = mx + alfa * (x2 - mx),
		yy2 = my + alfa * (y2 - my) + height;
	xx2 = x2 + beta * (xx2 - x2);
	yy2 = y2 + beta * (yy2 - y2);

//	dy = 1.6 * dir
	dy = 2 * dir;
	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 += '<path d="M'
	else
		output += '<path class="stroke" stroke-dasharray="5,5" d="M';
	out_sxsy(x1, ' ', y1);
	output += 'c' +
		((xx1 - x1) / stv_g.scale).toFixed(1) + ' ' +
		((y1 - yy1) / scale_y).toFixed(1) + ' ' +
		((xx2 - x1) / stv_g.scale).toFixed(1) + ' ' +
		((y1 - yy2) / scale_y).toFixed(1) + ' ' +
		((x2 - x1) / stv_g.scale).toFixed(1) + ' ' +
		((y1 - y2) / scale_y).toFixed(1)

	if (!dotted)
		output += '\n\tv' +
			(-dz).toFixed(1) + 'c' +
			((xx2 - dx - x2) / stv_g.scale).toFixed(1) + ' ' +
			((y2 + dz - yy2 - dy) / scale_y).toFixed(1) + ' ' +
			((xx1 + dx - x2) / stv_g.scale).toFixed(1) + ' ' +
			((y2 + dz - yy1 - dy) / scale_y).toFixed(1) + ' ' +
			((x1 - x2) / stv_g.scale).toFixed(1) + ' ' +
			((y2 - y1) / scale_y).toFixed(1)
	output += '"/>\n'
}

// draw a slur between two chords / notes
/* (the staves are not yet defined) */
/* (delayed output) */
/* (not a pretty routine, this) */
function draw_slur(path,	// list of symbols under the slur
		   sl,		// slur variables: type, end symbol, note
		   recurr) {	// recurrent call when slur on two staves
    var	i,
	k, g, x1, y1, x2, y2, height, addy, s_st2,
	a, y, z, h, dx, dy,
	ty = sl.ty,
	dir = (ty & 0x07) == C.SL_ABOVE ? 1 : -1,
	n = path.length,
	i1 = 0,
	i2 = n - 1,
	not1 = sl.nts,		// if the slur starts on a note
	k1 = path[0],
	k2 = path[i2],
	nn = 1

	set_dscale(k1.st)

	for (i = 1; i < n; i++) {
		k = path[i]
		if (k.type == C.NOTE || k.type == C.REST) {
			nn++
			if (k.st != k1.st
			 && !s_st2)
				s_st2 = k
		}
	}

	// if slur on 2 staves, define it, but don't output it now
	// this will be done in draw_sl2()
	if (s_st2 && !recurr) {			// if not 2nd call to draw_slur()
		if (!gene.a_sl)
			gene.a_sl = []

		// replace the symbols of the other staff
		// by symbols in the current staff but with updated y offsets
		h = 24 + k1.fmt.sysstaffsep		// delta y
		if (s_st2.st > k1.st)
			h = -h
		for (i = 0; i < n; i++) {
			k = path[i]
			if (k.st == k1.st) {
				if (k.dur)
					a = k		// (used for types // and \\)
				continue
			}
			k = clone(k)
			if (path[i] == s_st2)
				s_st2 = k
			path[i] = k
			if (k.dur) {
				k.notes = clone(k.notes)
				k.notes[0] = clone(k.notes[0])
				if (sl.ty & C.SL_CENTER) {
					if (k.st != a.st) {
						sl.ty = (sl.ty & ~0x07)
							 | (a.st < k.st
								? C.SL_BELOW
								: C.SL_ABOVE)
						z = k1.ymn
						h = k2.ymx
						if (k.st < a.st) {
							for (i1 = 1; i1 < i; i1++) {
								a = path[i1]
								if (a.ymn < z)
									z = a.ymn
							}
							for (i1 = i; i1 < i2; i1++) {
								a = path[i1]
								if (a.ymx > h)
									h = a.ymx
							}
						} else {
							for (i1 = 1; i1 < i; i1++) {
								a = path[i1]
								if (a.ymx > h)
									h = a.ymx
							}
							for (i1 = i; i1 < i2; i1++) {
								a = path[i1]
								if (a.ymn < z)
									z = a.ymn
							}
						}
						h += z
						a = k
					}
					k.y = h - k.y
					k.notes[0].pit = (k.y / 3 | 0) + 18
					k.ys = h - k.ys
					y = k.ymx
					k.ymx = h - k.ymn
					k.ymn = h - y
					k.stem = -k.stem
				} else {
					k.notes[0].pit += h / 3 | 0
					k.ys += h
					k.y += h
					k.ymx += h
					k.ymn += h
				}
			}
//			k.st = k1.st	// keep the staff number for draw_sl2()
		}

		ty = k1.st > s_st2.st ? '/' : '\\'
		if (sl.ty & C.SL_CENTER)
			ty = ty + ty			// type = // or \\
		else if (k1.st == k2.st)
			ty = ty == '/' ? '/\\' : '\\/'	// type = /\ or \/
		else
			ty += dir > 0 ? '+' : '-'	// type = .+ or .-

	    var	savout = output
		output = ""
		draw_slur(path, sl, 1 /*true*/)
		gene.a_sl.push([k1, s_st2, ty, output])
		output = savout
		return
	}

	/* fix endpoints */
	x1 = k1.x
	if (k1.notes && k1.notes[0].shhd)
		x1 += k1.notes[0].shhd;
	x2 = k2.x
	if (k2.notes)
		x2 += k2.notes[0].shhd

	if (not1) {					// start on a note
		y1 = 3 * (not1.pit - 18) + 2 * dir
		x1 += 3
	} else {					// start on a chord
		y1 = dir > 0 ? k1.ymx + 2 : k1.ymn - 2
		if (k1.type == C.NOTE) {
			if (dir > 0) {
				if (k1.stem > 0) {
					x1 += 5
					if (k1.beam_end
					 && k1.nflags >= -1	/* if with a stem */
//fixme: check if at end of tuplet
					 && !k1.in_tuplet) {
						if (k1.nflags > 0) {
							x1 += 2;
							y1 = k1.ys - 3
						} else {
							y1 = k1.ys - 6
						}
					} else {
						y1 = k1.ys + 3
					}
				} else {
					y1 = k1.y + 8
				}
			} else {
				if (k1.stem < 0) {
					x1 -= 1
					if (k2.grace) {
						y1 = k1.y - 8
					} else if (k1.beam_end
						&& k1.nflags >= -1
						&& (!k1.in_tuplet
						 || k1.ys < y1 + 3)) {
						if (k1.nflags > 0) {
							x1 += 2;
							y1 = k1.ys + 3
						} else {
							y1 = k1.ys + 6
						}
					} else {
						y1 = k1.ys - 3
					}
				} else {
					y1 = k1.y - 5
				}
			}
		}
	}

	if (sl.nte) {					// slur ending on a note
		y2 = 3 * (sl.nte.pit - 18) + 2 * dir
		x2 -= 3
	} else {					// end on a chord
		y2 = dir > 0 ? k2.ymx + 2 : k2.ymn - 2
		if (k2.type == C.NOTE) {
			if (dir > 0) {
				if (k2.stem > 0) {
					x2 += 1
					if (k2.beam_st
					 && k2.nflags >= -1
					 && !k2.in_tuplet)
						y2 = k2.ys - 6
					else
						y2 = k2.ys + 3
				} else {
					y2 = k2.y + 8
				}
			} else {
				if (k2.stem < 0) {
					x2 -= 5
					if (k2.beam_st
					 && k2.nflags >= -1
					 && !k2.in_tuplet)
//						|| k2.ys < y2 + 3))
						y2 = k2.ys + 6
					else
						y2 = k2.ys - 3
				} else {
					y2 = k2.y - 5
				}
			}
		}
	}

	if (k1.type != C.NOTE) {
		y1 = y2 + 1.2 * dir;
		x1 = k1.x + k1.wr * .5
		if (x1 > x2 - 12)
			x1 = x2 - 12
	}

	if (k2.type != C.NOTE) {
		if (k1.type == C.NOTE)
			y2 = y1 + 1.2 * dir
		else
			y2 = y1
		if (k1 != k2)
			x2 = k2.x - k2.wl * .3
	}

	if (nn >= 3) {
		k = path[1]
		if (k.type != C.BAR
		 && k.x < x1 + 48) {
			if (dir > 0) {
				y = k.ymx - 2
				if (y1 < y)
					y1 = y
			} else {
				y = k.ymn + 2
				if (y1 > y)
					y1 = y
			}
		}
		k = path[i2 - 1]
		if (k.type != C.BAR
		 && k.x > x2 - 48) {
			if (dir > 0) {
				y = k.ymx - 2
				if (y2 < y)
					y2 = y
			} else {
				y = k.ymn + 2
				if (y2 > y)
					y2 = y
			}
		}
	}

	a = (y2 - y1) / (x2 - x1)		/* slur steepness */
	if (a > SLUR_SLOPE || a < -SLUR_SLOPE) {
		a = a > SLUR_SLOPE ? SLUR_SLOPE : -SLUR_SLOPE
		if (a * dir > 0)
			y1 = y2 - a * (x2 - x1)
		else
			y2 = y1 + a * (x2 - x1)
	}

	/* for big vertical jump, shift endpoints */
	y = y2 - y1
	if (y > 8)
		y = 8
	else if (y < -8)
		y = -8
	z = y
	if (z < 0)
		z = -z;
	dx = .5 * z;
	dy = .3 * y
	if (y * dir > 0) {
		x2 -= dx;
		y2 -= dy
	} else {
		x1 += dx;
		y1 += dy
	}

	/* special case for grace notes */
	if (k1.grace)
		x1 = k1.x - GSTEM_XOFF * .5
	if (k2.grace)
		x2 = k2.x + GSTEM_XOFF * .5

	h = 0;
	a = (y2 - y1) / (x2 - x1)
	if (k1 != k2
	 && k1.v == k2.v) {
	    addy = y1 - a * x1
	    for (i = 1; i < i2; i++) {
		k = path[i]
		switch (k.type) {
		case C.NOTE:
		case C.REST:
			if (dir > 0) {
				y = 3 * (k.notes[k.nhd].pit - 18) + 6
				if (y < k.ymx)
					y = k.ymx;
				y -= a * k.x + addy
				if (y > h)
					h = y
			} else {
				y = 3 * (k.notes[0].pit - 18) - 6
				if (y > k.ymn)
					y = k.ymn;
				y -= a * k.x + addy
				if (y < h)
					h = y
			}
			break
		case C.GRACE:
			for (g = k.extra; g; g = g.next) {
				if (dir > 0) {
//					y = 3 * (g.notes[g.nhd].pit - 18) + 6
//					if (y < g.ymx)
						y = g.ymx;
					y -= a * g.x + addy
					if (y > h)
						h = y
				} else {
//					y = 3 * (g.notes[0].pit - 18) - 6
//					if (y > g.ymn)
						y = g.ymn;
					y -= a * g.x + addy
					if (y < h)
						h = y
				}
			}
			break
		}
	    }
	    y1 += .45 * h;
	    y2 += .45 * h;
	    h *= .65
	}

	if (nn > 3)
		height = (.08 * (x2 - x1) + 12) * dir
	else
		height = (.03 * (x2 - x1) + 8) * dir
	if (dir > 0) {
		if (height < 3 * h)
			height = 3 * h
		if (height > 40)
			height = 40
	} else {
		if (height > 3 * h)
			height = 3 * h
		if (height < -40)
			height = -40
	}

	y = y2 - y1
	if (y < 0)
		y = -y
	if (dir > 0) {
		if (height < .8 * y)
			height = .8 * y
	} else {
		if (height > -.8 * y)
			height = -.8 * y
	}
	height *= k1.fmt.slurheight;

//	anno_start(k1_o, 'slur');
	slur_out(x1, y1, x2, y2, dir, height, ty & C.SL_DOTTED);
//	anno_stop(k1_o, 'slur');

	/* have room for other symbols */
	dx = x2 - x1;
	a = (y2 - y1) / dx;
/*fixme: it seems to work with .4, but why?*/
//	addy = y1 - a * x1 + .4 * height
//fixme: the following code seems better!
	addy = y1 - a * x1
	if (height > 0)
		addy += 3 * Math.sqrt(height) - 2
	else
		addy -= 3 * Math.sqrt(-height) - 2
	for (i = 0; i <= i2; i++) {
		k = path[i]
		if (k.st != k1.st || k.type == C.BAR)
			continue
		y = a * k.x + addy
		if (k.ymx < y)
			k.ymx = y
		else if (k.ymn > y)
			k.ymn = y
		if (recurr)			// no room when slur on 2 staves
			continue
		if (i == i2) {
			dx = x2
			if (sl.nte)
				dx -= 5;
		} else {
			dx = k.x + k.wr
		}
		if (i != 0)
			x1 = k.x
		if (!i || i == i2)
			y -= height / 3
		dx -= x1 - k.wl
		y_set(k1.st, dir > 0, x1 - k.wl, dx, y)
	}
}

/* -- draw the slurs between 2 symbols --*/
function draw_slurs(s, last) {
    var	gr1, i, m, note, sls, nsls

	// draw a slur knowing the start and stop elements
	function draw_sls(s,		// start symbol
			sl) {		// slur variables
	    var	k, v, i, dir, s3,
		path = [],
		s2 = sl.se			// slur end

		if (last && s2.time > last.time)
			return			// will be drawn next time

		// handle slurs without start or end
		switch (sl.loc) {
		case 'i':			// no start
			s = prev_scut(s)
			break
		case 'o':			// no end
			for (s3 = gr1 || s; s3.ts_next; s3 = s3.ts_next)
				;
			s2 = s3
			for (; s3; s3 = s3.ts_prev) {
				if (s3.v == s.v) {
					s2 = s3
					break
				}
				if (s3.st == s.st)
					s2 = s3
				if (s3.ts_prev.time != s2.time)
					break
			}
			break
		}

		// if the slur continues on the next music line,
		// stop it at the end of the current line
		if (s.p_v.s_next && s2.time >= tsnext.time) {
		  if (s2.time == tsnext.time) {
		    if (s2.grace) {
			for (s3 = tsnext; s3 && s3.time == s2.time; s3 = s3.ts_next) {
				if (s3.type == C.GRACE) {
					s3 = null
					break
				}
			}
		    } else {
			for (s3 = tsnext; s3.time == s2.time; s3 = s3.ts_next) {
				if (s3 == s2) {
					s3 = null	// end of slur in next line
					break
				}
			}
		    }
		  } else {
			s3 = null
		  }
		    if (!s3) {
			s.p_v.sls.push(sl);		// continuation on next line
			s2 = s.p_v.s_next.prev		// one voice
			while (s2.next)
				s2 = s2.next;		// search the ending bar
			sl = Object.create(sl);		// new slur
		    }
		}

		// set the slur position
		switch (sl.ty & 0x07) {
		case C.SL_ABOVE: dir = 1; break
		case C.SL_BELOW: dir = -1; break
		default:
			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 (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

		if (gr1				// if start on a grace note
		 && !(s2.grace			// and not end in the same
		   && s.v == s2.v		// grace note sequence
		   && s.time == s2.time)) {
			do {
				path.push(s);	// add all grace notes
				s = s.next
			} while (s);
			s = gr1.next
		} else {
			path.push(s);
			if (s.grace)
				s = s.next
			else
				s = s.ts_next
		}

		if (!s2.grace) {		// if end on a normal note
			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)
					path.push(s)
				if (s == s2)
					break
				s = s.next
			}
		} else {			// end on a grace note
			k = s2
			while (k.prev)
				k = k.prev	// .extra pointer
			while (1) {
				if (s.v == v)
					path.push(s)
				if (s.extra == k)
					break
				s = s.ts_next
			}
			s = k
			while (1) {
				path.push(s)
				if (s == s2)
					break
				s = s.next
			}
		}

		// if some nested slurs/tuplets, draw them
		for (i = 1; i < path.length - 1; i++) {
			s = path[i]
			if (s.sls)
				draw_slurs(s, last)
			if (s.tp)
				draw_tuplet(s)
		}
		draw_slur(path, sl)
		return 1			// slur drawn, remove it
	} // draw_sls()

	// code of draw_slurs()
	while (1) {
		if (!s || s == last) {
			if (!gr1		// if end of grace notes
			 || !(s = gr1.next)	// return to normal notes
			 || s == last)
				break
			gr1 = null
		}
		if (s.type == C.GRACE) {	// if start of grace notes
			gr1 = s;		// continue in the grace note sequence
			s = s.extra
			continue
		}
		if (s.sls) {			// slurs from the chord
			sls = s.sls
			s.sls = null
			nsls = []
			for (i = 0; i < sls.length; i++) {
				if (!draw_sls(s, sls[i]))
					nsls.push(sls[i])
			}
			if (nsls.length)
				s.sls = nsls
		}
		s = s.next
	}
}

/* -- draw a tuplet -- */
/* (the staves are not yet defined) */
/* (delayed output) */
/* See http://moinejf.free.fr/abcm2ps-doc/tuplets.html
 * for the value of 'tp.f' */
function draw_tuplet(s1) {
    var	s2, s3, g, stu, std, nb_only,
	x1, x2, y1, y2, xm, ym, a, s0, yy, yx, dy, a, dir, r,
	tp = s1.tp.shift()		// tuplet parameters

	if (!s1.tp.length)
		delete s1.tp		// last tuplet

	// treat the slurs and the nested tuplets
	stu = std = s1.st
	for (s2 = s1; s2; s2 = s2.next) {
		switch (s2.type) {
		case C.GRACE:
			if (!s2.sl1)
				continue
			for (g = s2.extra; g; g = g.next) {
				if (g.sls)
					draw_slurs(g)
			}
			// fall thru
		default:
			continue
		case C.NOTE:
		case C.REST:
			break
		}
		if (s2.sls)
			draw_slurs(s2)
		if (s2.st < stu) {
			std = stu
			stu = s2.st
		} else if (s2.st > std) {
			std = s2.st
		}
		if (s2.tp)
			draw_tuplet(s2)
		if (s2.tpe)
			break
	}

	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
	}

	dir = tp.f[3]				// 'where'
	if (!dir) {				// if auto
		s3 = s1
		while (s3 && !s3.stem)		// (may have tuplets of rests!)
			s3 = s3.next
		dir = (s3 && s3.stem < 0) ? C.SL_BELOW : C.SL_ABOVE
	}
	set_dscale(dir == C.SL_ABOVE ? stu : std)

	if (s1 == s2				// tuplet with 1 note (!)
	 || tp.f[1] == 2) {			// what == nothing
		nb_only = true
	} else if (tp.f[1] == 1) {			/* 'what' == slur */
		nb_only = true;
		draw_slur([s1, s2], {ty: dir})
	} else {

		/* search if a bracket is needed */
		if (tp.f[0] != 2		// if 'when' != always
		 && s1.type == C.NOTE && s2.type == C.NOTE) {
			nb_only = true
			for (s3 = s1; ; s3 = s3.next) {
				if (s3.type != C.NOTE
				 && s3.type != C.REST) {
					if (s3.type == C.GRACE
					 || s3.type == C.SPACE)
						continue
					nb_only = false
					break
				}
				if (s3 == s2)
					break
				if (s3.beam_end) {
					nb_only = false
					break
				}
			}
			if (nb_only
			 && !s1.beam_st
			 && !s1.beam_br1
			 && !s1.beam_br2) {
				for (s3 = s1.prev; s3; s3 = s3.prev) {
					if (s3.type == C.NOTE
					 || s3.type == C.REST) {
						if (s3.nflags >= s1.nflags)
							nb_only = false
						break
					}
				}
			}
			if (nb_only && !s2.beam_end) {
				for (s3 = s2.next; s3; s3 = s3.next) {
					if (s3.type == C.NOTE
					 || s3.type == C.REST) {
						if (!s3.beam_br1
						 && !s3.beam_br2
						 && s3.nflags >= s2.nflags)
							nb_only = false
						break
					}
				}
			}
		}
	}

	/* 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
		if (dir == C.SL_ABOVE)		// 8 = width around the middle
			ym = y_get(stu, 1, xm - 4, 8)
		else
			ym = y_get(std, 0, xm - 4, 8) -
					gene.curfont.size

		if (s1.stem * s2.stem > 0) {
			if (s1.stem > 0)
				xm += 4
			else
				xm -= 4
		}

		yy = ym + gene.curfont.size * .22
		if (tp.f[2] == 0)		// if 'which' == number
			xy_str(xm, yy, tp.p.toString(), 'c')
		else
			xy_str(xm, yy, tp.p + ':' + tp.q, 'c')

		for (s3 = s1; ; s3 = s3.next) {
			if (s3.x >= xm)
				break
		}
		if (dir == C.SL_ABOVE) {
			ym += gene.curfont.size
			if (s3.ymx < ym)
				s3.ymx = ym;
			y_set(stu, 1, xm - 3, 6, ym)
		} else {
			if (s3.ymn > ym)
				s3.ymn = ym;
			y_set(std, 0, xm - 3, 6, ym)
		}
		return
	}

	// 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 == stu
		 && s2.stem > 0)
			x2 += 3.5
	}

    // above
    if (dir == C.SL_ABOVE) {
	if (s1.st >= s2.st) {
		if (s1.stem > 0)
			x1 += 3
		ym = y_get(s1.st, 1, x1 - 4, 8)
		y1 = ym > staff_tb[s1.st].topbar + 2
			? ym
			: staff_tb[s1.st].topbar + 2
	} else {
		y1 = staff_tb[s1.st].topbar + 2
	}
	if (s2.st >= s1.st) {
		ym = y_get(s2.st, 1, x2 - 4, 8)
		y2 = ym > staff_tb[s2.st].topbar + 2
			? ym
			: staff_tb[s2.st].topbar + 2
	} else {
		y2 = staff_tb[s2.st].topbar + 2
	}

	xm = .5 * (x1 + x2);
	ym = .5 * (y1 + y2);

	a = (y2 - y1) / (x2 - x1);
	s0 = 3 * (s2.notes[s2.nhd].pit - s1.notes[s1.nhd].pit) / (x2 - x1)
	if (s0 > 0) {
		if (a < 0)
			a = 0
		else if (a > s0)
			a = s0
	} else {
		if (a > 0)
			a = 0
		else if (a < s0)
			a = s0
	}
	a = s1.fmt.beamslope * a / (s1.fmt.beamslope + Math.abs(a))
	if (a * a < .1 * .1)
		a = 0

	/* shift up bracket if needed */
	dy = 0
	for (s3 = s1; ; s3 = s3.next) {
		if (!s3.dur			/* not a note or a rest */
		 || s3.st != stu) {
			if (s3 == s2)
				break
			continue
		}
		yy = ym + (s3.x - xm) * a;
		yx = y_get(stu, 1, s3.x - 4, 8) + 2
		if (yx - yy > dy)
			dy = yx - yy
		if (s3 == s2)
			break
	}

	ym += dy;
	y1 = ym + a * (x1 - xm);
	y2 = ym + a * (x2 - xm);

	/* shift the slurs / decorations */
	ym += 6
	for (s3 = s1; ; s3 = s3.next) {
		if (s3.st == stu) {
			yy = ym + (s3.x - xm) * a
			if (s3.ymx < yy)
				s3.ymx = yy
			y_set(stu, 1, s3.x - 3, 6, yy)
		}
		if (s3 == s2)
			break
	}

    // below
    } else {	/* lower voice of the staff: the bracket is below the staff */
	if (s1.st <= s2.st) {
		ym = y_get(s1.st, 0, x1 - 4, 8)
		y1 = ym < -2
			? ym
			: -2
	} else {
		y1 = -2
	}
	if (s2.st <= s1.st) {
		if (s2.stem < 0)
			x2 -= 3
		ym = y_get(s2.st, 0, x2 - 4, 8)
		y2 = ym < -2
			? ym
			: -2
	} else {
		y2 = -2
	}

	xm = .5 * (x1 + x2);
	ym = .5 * (y1 + y2);

	a = (y2 - y1) / (x2 - x1);
	s0 = 3 * (s2.notes[0].pit - s1.notes[0].pit) / (x2 - x1)
	if (s0 > 0) {
		if (a < 0)
			a = 0
		else if (a > s0)
			a = s0
		if (a > .35)
			a = .35
	} else {
		if (a > 0)
			a = 0
		else if (a < s0)
			a = s0
		if (a < -.35)
			a = -.35
	}
	if (a * a < .1 * .1)
		a = 0

	/* shift down the bracket if needed */
	dy = 0
	for (s3 = s1; ; s3 = s3.next) {
		if (!s3.dur			/* not a note nor a rest */
		 || s3.st != std) {
			if (s3 == s2)
				break
			continue
		}
		yy = ym + (s3.x - xm) * a;
		yx = y_get(std, 0, s3.x - 4, 8)
		if (yx - yy < dy)
			dy = yx - yy
		if (s3 == s2)
			break
	}

	ym += dy - 8
	y1 = ym + a * (x1 - xm);
	y2 = ym + a * (x2 - xm);

	/* shift the slurs / decorations */
	ym -= 2
	for (s3 = s1; ; s3 = s3.next) {
		if (s3.st == std) {
			yy = ym + (s3.x - xm) * a
			if (s3.ymn > yy)
				s3.ymn = yy;
			y_set(std, 0, s3.x - 3, 6, yy)
		}
		if (s3 == s2)
			break
	}
    } /* lower voice */

	if (tp.f[2] == 1) {			/* if 'which' == none */
		out_tubr(x1, y1 + 4, x2 - x1, y2 - y1, dir == C.SL_ABOVE);
		return
	}
	out_tubrn(x1, y1, x2 - x1, y2 - y1, dir == C.SL_ABOVE,
		tp.f[2] == 0 ? tp.p.toString() : tp.p + ':' +  tp.q);

	if (dir == C.SL_ABOVE)
		y_set(stu, 1, xm - 3, 6, yy + 2)
	else
		y_set(std, 0, xm - 3, 6, yy)
}

// -- draw a ties --
function draw_tie(not1, not2,
			job) {	// -: start and end
				// 1: no starting note
				// 2: no ending note
				// 3: no start for clef or staff change
    var	m, x1, s, y, h, time,
	p = job == 2 ? not1.pit : not2.pit,
	dir = (not1.tie_ty & 0x07) == C.SL_ABOVE ? 1 : -1,
	s1 = not1.s,
	st = s1.st,
	s2 = not2.s,
	x2 = s2.x,
	sh = not1.shhd			// head shift

	for (m = 0; m < s1.nhd; m++)
		if (s1.notes[m] == not1)
			break
	if (dir > 0) {
		if (m < s1.nhd && p + 1 == s1.notes[m + 1].pit)
			if (s1.notes[m + 1].shhd > sh)
				sh = s1.notes[m + 1].shhd
	} 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

	if (job != 2) {
		for (m = 0; m < s2.nhd; m++)
			if (s2.notes[m] == not2)
				break
		sh = s2.notes[m].shhd
		if (dir > 0) {
			if (m < s2.nhd && p + 1 == s2.notes[m + 1].pit)
				if (s2.notes[m + 1].shhd < sh)
					sh = s2.notes[m + 1].shhd
		} 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
	}

	switch (job) {
	default:
		if (p < not2.pit || dir < 0)
			p = not1.pit
		break
	case 3:
		dir = -dir
	case 1:				/* no starting note */
		x1 = s2.prev ? (s2.prev.x + s2.wr) : s1.x
		if (s1.st != s2.st)
			st = s2.st
		x1 += (x2 - x1) * .4
		if (x1 > x2 - 20)
			x1 = x2 - 20
		break
	case 2:				 // no ending note
		x2 = s1.next ? s1.next.x : realwidth
		if (x2 != realwidth)
			x2 -= (x2 - x1) * .4
		if (x2 < x1 + 16)
			x2 = x1 + 16
		break
	}
	if (x2 - x1 > 20) {
		x1 += 3.5
		x2 -= 3.5
	} else {
		x1 += 1.5
		x2 -= 1.5
	}

	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) + .8 * dir
	h = (.15 * (x2 - x1) + 14) * dir * s1.fmt.tieheight
//	anno_start(k1, 'slur')
	slur_out(x1, y, x2, y, dir, h, not1.tie_ty & C.SL_DOTTED)
//	anno_stop(k1, 'slur')
} // draw_tie()

/* -- draw all ties between neighboring notes -- */
function draw_all_ties(p_voice) {
    var s, s1, s2, clef_chg, x, dx, m, not1, not2,
	tim2 = 0

	/* search the start of ties */
//	clef_chg = false
	s1 = p_voice.sym
	set_color(s1.color)
	for ( ; s1; s1 = s1.next) {
		if (s1.ti2			// if end of tie
		 && !s1.invis
		 && s1.time != tim2) {		// and new end
			for (m = 0; m <= s1.nhd; m++) {
				not2 = s1.notes[m]
				not1 = not2.tie_s
				if (!not1
				 || not1.s.v != s1.v)	// (already done)
					continue
				draw_tie(not1, not2, 1)
			}
		}
		if (!s1.ti1			// if not start of tie
		 || s1.invis)
			continue

		// get the end of the tie(s)
		if (s1.type == C.GRACE) {
			for (s = s1.extra; s; s = s.next) {
			    for (m = 0; m <= s1.nhd; m++) {
				not1 = s.notes[m]
				not2 = not1.tie_e
				if (!not2)
					continue
				draw_tie(not1, not2)
				tim2 = not2.s.time
			    }
			}
			continue
		}

	    for (m = 0; m <= s1.nhd; m++) {
		not1 = s1.notes[m]
		not2 = not1.tie_e
		if (!not2) {
			if (not1.tie_ty)
				draw_tie(not1, not1, 2)
			continue
		}
		s2 = not2.s
		if (tsnext && s2.time >= tsnext.time) {	// if end in next line
			draw_tie(not1, not2, 2)
			continue
		}
		tim2 = s2.time

		// check if some clef changes (can occur in an other voice)
		for (s = s1.ts_next; s != s2; s = s.ts_next) {
			if (s.st != s1.st)
				continue
			if (s.type == C.CLEF) {
				clef_chg = true
				break
			}
		}

		/* ties with clef or staff change */
		if (clef_chg || s1.st != s2.st) {
			draw_tie(not1, not2, 2)
			draw_tie(not1, not2, 3)
			clef_chg = false
		} else {
			draw_tie(not1, not2)
		}
	    }
	}
}

/* -- draw the symbols near the notes -- */
/* (the staves are not yet defined) */
/* order:
 * - scaled
 *   - beams
 *   - decorations near the notes
 *   - decorations tied to the notes
 *   - tuplets and slurs
 * - not scaled
 *   - measure numbers
 *   - lyrics
 *   - staff decorations
 *   - chord symbols
 *   - repeat brackets
 *   - parts and tempos
 * The buffer output is delayed until the definition of the staff system
 */
function draw_sym_near() {
    var	p_voice, p_st, s, v, st, y, g, w, i, st, dx, top, bot, ymn,
	output_sav = output;

	// 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 = ""
	YSTEP = Math.ceil(realwidth / 2)

	// initialize the min/max vertical offsets in the staves
	for (st = 0; st <= nstaff; st++) {
		p_st = staff_tb[st]
		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;

		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) {
						self.calculate_beam(bm, g)
						if (bm.s2)
							set_yab(g, bm.s2)
					}
				}
				if (!s.p_v.ckey.k_bagpipe	// no slur when bagpipe
				 && s.fmt.graceslurs
				 && !s.gr_shift			// tied to previous note
				 && !s.sl1			// explicit slur
				 && !s.ti1			// some tie
				 && s.next
				 && s.next.type == C.NOTE)
					grace_slur(s)
				break
			}
		}
		for (s = p_voice.sym; s; s = s.next) {
			switch (s.type) {
			case C.NOTE:
				if ((s.beam_st && !s.beam_end)
				 || (first_note && !s.beam_st)) {
					first_note = false;
					self.calculate_beam(bm, s)
					if (bm.s2)
						set_yab(s, bm.s2)
				}
				break
			}
		}
	}

	set_tie_room();
	draw_deco_near()

	/* set the min/max vertical offsets */
	for (s = tsfirst; s; s = s.ts_next) {
		if (s.invis)
			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,
//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
		default:
			y_set(s.st, true, s.x - s.wl, s.wl + s.wr, s.ymx + 2);
			y_set(s.st, false, s.x - s.wl, s.wl + s.wr, s.ymn - 2)
			// fall thru
		case C.PART:
		case C.TEMPO:
		case C.STAVES:
			continue
		case C.NOTE:
			break
		}

		// (permit closer staves)
		if (s.stem > 0) {
			if (s.stemless) {
				dx = -5;
				w = 10
			} else if (s.beam_st) {
				dx = 3;
				w = s.beam_end ? 4 : 10
			} else {
				dx = -8;
				w = s.beam_end ? 11 : 16
			}
			y_set(s.st, true, s.x + dx, w, s.ymx);
			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
			} else if (s.beam_st) {
				dx = -6;
				w = s.beam_end ? 4 : 10
			} else {
				dx = -8;
				w = s.beam_end ? 5 : 16
			}
			dx += s.notes[0].shhd;
			y_set(s.st, false, s.x + dx, w, s.ymn)
		}

		/* have room for the accidentals */
		if (s.notes[s.nhd].acc) {
			y = 3 * (s.notes[s.nhd].pit - 18)
				+ (s.notes[s.nhd].acc == -1	// flat
					? 11 : 10)
			y_set(s.st, true, s.x - 10, 10, y)
		}
		if (s.notes[0].acc) {
			y = 3 * (s.notes[0].pit - 18)
				- (s.notes[0].acc == -1		// flat
					? 5 : 10)
			y_set(s.st, false, s.x - 10, 10, y)
		}
	}

	draw_deco_note()

	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v];
		s = p_voice.sym
		if (!s)
			continue
		set_color(s.color);
		st = p_voice.st;
//  if (st == undefined) {
//error(1, s, "BUG: no staff for voice " + p_voice.id)
//    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)
		}
	}
	set_color()

	/* set the top and bottom out of the staves */
	for (st = 0; st <= nstaff; st++) {
		p_st = staff_tb[st];
		top = p_st.topbar + 2;
		bot = p_st.botbar - 2
/*fixme:should handle stafflines changes*/
		for (i = 0; i < YSTEP; i++) {
			if (top > p_st.top[i])
				p_st.top[i] = top
			if (bot < p_st.bot[i])
				p_st.bot[i] = bot
		}
	}

	if (cfmt.measurenb >= 0)
		draw_measnb();

	/* if any lyric, draw them now as unscaled */
	set_dscale(-1)
//	set_sscale(-1)
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (p_voice.have_ly) {
			draw_all_lyrics()
			break
		}
	}

	draw_deco_staff()

	draw_partempo()			// draw the parts and tempo indications if any

	set_dscale(-1);
	output = output_sav
}

/* -- draw the name/subname of the voices -- */
function draw_vname(indent, stl) {
    var	p_voice, n, st, v, a_p, p, y, h, h2,
	staff_d = []

	if (!gene.vnt)
		return

//	for (st = stl.length; st >= 0; st--) {
	for (st = stl.length; --st >= 0; ) {
		if (stl[st])
			break
	}
	if (st < 0)
		return

	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (!cur_sy.voices[v])
			continue
		st = cur_sy.voices[v].st
		if (!stl[st])
			continue
		p = gene.vnt == 1 ? p_voice.nm : p_voice.snm
		if (!p)
			continue
		delete p_voice.new_name
		if (!staff_d[st])
			staff_d[st] = p
		else
			staff_d[st] += "\n" + p
	}
	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");
		y = staff_tb[st].y
			+ staff_tb[st].topbar * .5
				* staff_tb[st].staffscale
			+ h2 * (a_p.length - 2) + h *.22

		// if instrument with 2 staves, center the voice name
		if ((cur_sy.staves[st].flags & OPEN_BRACE)
		 && st + 1 < staff_tb.length
		 && (cur_sy.staves[st + 1].flags & CLOSE_BRACE)
		 && !staff_d[st + 1])
			y -= (staff_tb[st].y - staff_tb[st + 1].y) * .5
		for (n = 0; n < a_p.length; n++) {
			p = a_p[n];
			xy_str(indent, y, p, "c");
			y -= h
		}
	}
}

// -- set the y offset of the staves and return the height of the whole system --
function set_staff() {
    var	i, st, prev_staff, v, fmt, s,
	y, staffsep, dy, maxsep, mbot, val, p_voice, p_staff,
	sy = cur_sy

	// the last values of {,max}{,sys}staffsep are in the last format
	fmt = tsnext ? tsnext.fmt : cfmt

	/* set the scale of the voices */
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (p_voice.scale != 1)
			p_voice.scale_str = 
				'transform="scale(' + p_voice.scale + ')"'
	}

	// search the top staff
	for (st = 0; st <= nstaff; st++) {
		if (gene.st_print[st])
			break
	}
	y = 0
	if (st > nstaff)
		return y

		p_staff = staff_tb[st]
		for (i = 0; i < YSTEP; i++) {
			val = p_staff.top[i]
			if (y < val)
				y = val
		}

	/* set the vertical offset of the 1st staff */
	y *= p_staff.staffscale;
	staffsep = tsfirst.fmt.staffsep / 2 +
			p_staff.topbar * p_staff.staffscale
	if (y < staffsep)
		y = staffsep
	if (y < p_staff.ann_top)	// absolute annotation
		y = p_staff.ann_top;
	p_staff.y = -y;

	/* set the offset of the other staves */
	for (prev_staff = 0; prev_staff < st; prev_staff++)
		staff_tb[prev_staff].y = -y
	if (!gene.st_print[st])		// no staff
		return y

	var sy_staff_prev = sy.staves[prev_staff]
	for (st++; st <= nstaff; st++) {
		if (!gene.st_print[st])
			continue
		p_staff = staff_tb[st]
		staffsep = sy_staff_prev.sep || fmt.sysstaffsep;
		maxsep = sy_staff_prev.maxsep || fmt.maxsysstaffsep;

		dy = 0
		if (p_staff.staffscale == staff_tb[prev_staff].staffscale) {
			for (i = 0; i < YSTEP; i++) {
				val = p_staff.top[i] -
						staff_tb[prev_staff].bot[i]
				if (dy < val)
					dy = val
			}
			dy *= p_staff.staffscale
		} else {
			for (i = 0; i < YSTEP; i++) {
				val = p_staff.top[i] * p_staff.staffscale
				  - staff_tb[prev_staff].bot[i]
					* staff_tb[prev_staff].staffscale
				if (dy < val)
					dy = val
			}
		}
		staffsep += p_staff.topbar * p_staff.staffscale
		if (dy < staffsep)
			dy = staffsep;
		maxsep += p_staff.topbar * p_staff.staffscale
		if (dy > maxsep)
			dy = maxsep;
		y += dy;
		p_staff.y = -y;

		while (!gene.st_print[++prev_staff])
			staff_tb[prev_staff].y = -y
		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)
			mbot = val
	}
	if (mbot > p_staff.ann_bot) 	// absolute annotation
		mbot = p_staff.ann_bot;
	mbot *= staff_tb[prev_staff].staffscale

	/* output the staff offsets */
	for (st = 0; st <= nstaff; st++) {
		p_staff = staff_tb[st];
		dy = p_staff.y
		if (p_staff.staffscale != 1) {
			p_staff.scale_str =
				'transform="translate(0,' +
					(posy - dy).toFixed(1) + ') ' +
				'scale(' + p_staff.staffscale + ')"'
		}
	}

	if (mbot == 0) {
		for (st = nstaff; st >= 0; st--) {
			if (gene.st_print[st])
				break
		}
		if (st < 0)		/* no symbol in this system ! */
			return y
	}
	dy = -mbot;
	staffsep = fmt.staffsep * .5
	if (dy < staffsep)
		dy = staffsep;
	maxsep = fmt.maxstaffsep * .5
	if (dy > maxsep)
		dy = maxsep;

	// return the height of the whole staff system
	return y + dy
}

/* -- draw the staff systems and the measure bars -- */
function draw_systems(indent) {
	var	s, s2, st, x, x2, res, sy,
		xstaff = [],
		stl = [],		// all staves in the line
		bar_bot = [],
		bar_height = [],
		bar_ng = [],		// number of gaps
		ba = [],		// bars [symbol, bottom, height]
		sb = "",
		thb = ""

	/* -- set the bottom and height of the measure bars -- */
	function bar_set() {
	    var	st, sc, i, j, l, stlines, b, hlmap,
		dy = 0

		for (st = 0; st <= cur_sy.nstaff; st++) {
			if (xstaff[st] < 0) {
				bar_bot[st] = bar_height[st] = 0
				continue
			}
			sc = staff_tb[st].staffscale;
			stlines = cur_sy.staves[st].stafflines
			l = stlines.length
			for (i = 0; i < l; i++) {
				if (stlines[i] != '.' && stlines[i] != '-')
					break
			}
			bar_bot[st] = staff_tb[st].y + 6 * (
						i == l - 1 ? (l - 2) :
						i >= l - 2 ? (l - 3) :
						i) * sc
			if (!dy) {
				dy = staff_tb[st].y + 6 * (
						i == l ? (l + 1) :
						i >= l - 2 ? l :
						(l - 1)) * sc
			}
			bar_height[st] = dy - bar_bot[st];
			bar_ng[st] = l - i		// number of gaps
				 && (l - 1 - i || 2)

			// define the helper lines
			if (stlines[l-1]!= '.') {	// if any staff line
				staff_tb[st].hll = 17 + i * 2	// pitch of lowest note
							// without helper line
							// ('D' when standard staff)
				if (i == l)
					staff_tb[st].hll -= 2
				staff_tb[st].hlmap =
					hlmap = new Int8Array((l - i + 1) * 2 + 2)
				for (j = 1; i < l; i++, j += 2) {
					switch (stlines[i]) {
					case '|':
					case '[':
					case "'":
//					case ':':
						hlmap[j - 1] = 1	// no helper line
						hlmap[j] = 1
						hlmap[j + 1] = 1
						break
					}
				}
			}

			dy = (cur_sy.staves[st].flags & STOP_BAR) ?
					0 : bar_bot[st]
		}

		// if in the middle of the tune, check the previous bar(s)
		i = ba.length
		if (!i)
			return
		while (--i >= 0) {
			b = ba[i]
			st = b[0].st
			if (b[1] > bar_bot[st])
				b[1] = bar_bot[st]
			if (b[2] < bar_height[st])
				b[2] = bar_height[st]
			if (b[3] < bar_ng[st])
				b[3] = bar_ng[st]
			if (b[0].seqst)		// end of time sequence
				break
		}
	} // bar_set()

	/* -- draw a staff -- */
	function draw_staff(st, x1, x2) {
	    var	w, i, dy, ty,
			y = 0,
			ln = "",
		tycl = {
		"|": "slW",		// normal
		"[": "slthW",		// thick
		"'": "sltnW",		// thin
		":": "sldW"		// dash
		},
		stafflines = cur_sy.staves[st].stafflines,
			l = stafflines.length,
			il = 6 * staff_tb[st].staffscale // interline

		if (!/[\[|':]/.test(stafflines))	// '
			return				// no line
		w = x2 - x1;
		set_sscale(-1)

		// check if default staff
		if (cache && cache.st_l == stafflines
		 && staff_tb[st].staffscale == 1
		 && cache.st_w == (w | 0)) {
			xygl(x1, staff_tb[st].y, 'stdef' + cfmt.fullsvg)
			return
		}
		for (i = 0; i < l; i++, y -= il) {
			if (stafflines[i] == '.')
				continue
			dy = 0
			for (; i < l; i++, y -= il, dy -= il) {
				switch (stafflines[i]) {
				case '.':
				case '-':
					continue
				case ty:
					ln += 'm-' + w.toFixed(1) +
						' ' + dy.toFixed(2) +
						'h' + w.toFixed(1);
					dy = 0
					continue
				}
				if (ty != undefined)
					ln += '"/>\n';
				ty = stafflines[i]
				ln += '<path class="' + tycl[ty] +
					'" d="m0 ' + y + 'h' + w.toFixed(1);
				dy = 0
			}
			ln += '"/>'
		}
		y = staff_tb[st].y
		if (!cache
		 && w > get_lwidth() - 10
		 && staff_tb[st].staffscale == 1) {
			cache = {
				st_l: stafflines,
				st_w: w | 0
			}
			i = 'stdef' + cfmt.fullsvg;
			if (ln.indexOf('<path', 1) < 0)
				glyphs[i] = ln.replace('path', 'path id="' + i + '"')
			else
				glyphs[i] = '<g id="' + i + '">\n' + ln + '\n</g>';
			xygl(x1, y, i)
			return
		}
		out_XYAB('<g transform="translate(X, Y)">\n' + ln + '\n</g>\n', x1, y)
	} // draw_staff()

	// draw a measure bar
	function draw_bar(s, bot, h, ng) {
	    var	i, s2, yb, w,
		bar_type = s.bar_type,
		st = s.st,
		p_staff = staff_tb[st],
		top = ng >= 3 ? 6 * ng : (4 - ng) * 6,
		x = s.x

		// don't put a line between the staves if there is no bar above
		if (st != 0
		 && s.ts_prev
//fixme: 's.ts_prev.st != st - 1' when floating voice in lower staff
//	 && (s.ts_prev.type != C.BAR || s.ts_prev.st != st - 1))
		 && s.ts_prev.type != C.BAR)
			h = top * p_staff.staffscale

		s.ymx = s.ymn + h;

		set_sscale(-1)
		anno_start(s)
		if (s.color)
			set_color(s.color);

		// compute the middle vertical offset of the staff
		yb = bot + top / 2

		// if measure repeat, draw the '%' like glyphs
		if (s.bar_mrep) {
			set_sscale(st)
			if (s.bar_mrep == 1) {
				for (s2 = s.prev; s2.type != C.REST; s2 = s2.prev)
					;
				xygl(s2.x, yb, "mrep")
			} else {
				xygl(x, yb, "mrep2")
				if (s.v == cur_sy.top_voice)
					nrep_out(x, yb + p_staff.topbar, s.bar_mrep)
			}
			set_sscale(-1)
		}

		if (bar_type == '||:')
			bar_type = '[|:'

		for (i = bar_type.length; --i >= 0; ) {
			switch (bar_type[i]) {
			case "|":
				if (s.bar_dotted) {
					w = top / 6 <= 9
					    ? [0, 0, 4, 3.6, 4.8, 4.3, 4, 4.7, 4.4, 4.9]
							[top / 6]
						: 5
					out_XYAB(
			'<path class="bW" stroke-dasharray="A,A" d="MX YvG"/>\n',
						x, bot, w * p_staff.staffscale, -h)
				} else if (s.color) {
					out_XYAB('<path class="bW" d="MX YvF"/>\n',
						x, bot, -h)
				} else {
					sb += 'M' + sx(x).toFixed(1)
						+ ' ' + self.sy(bot).toFixed(1)
						+ 'v' + (-h).toFixed(1)
				}
				break
			default:
//			case "[":
//			case "]":
				x -= 3;
				if (s.color)
					out_XYAB('<path class="bthW" d="MX YvF"/>\n',
						x + 1.5, bot, -h)
				else
					thb += 'M' + sx(x + 1.5).toFixed(1)
						+ ' ' + self.sy(bot).toFixed(1)
						+ 'v' + (-h).toFixed(1)
				break
			case ":":
				x -= 2;
				set_sscale(st);
				if (ng & 1) {
					xygl(x, yb + 6, "rdot")
					xygl(x, yb - 6, "rdot")
				} else {
					xygl(x, yb - 12, "rdots")
				}
				set_sscale(-1)
				break
			}
			x -= 3
		}
		set_color();
		anno_stop(s)
	} // draw_bar()

	// output all the bars
	function out_bars() {
	    var	i, b,
		l = ba.length

		for (i = 0; i < l; i++) {
			b = ba[i]		// symbol, bottom, height, top
			draw_bar(b[0], b[1], b[2], b[3])
		}

		set_sscale(-1)
		if (sb)			// single bars
			output += '<path class="bW" d="'
				+ sb
				+ '"/>\n'

		if (thb)		// thick bars [x, y, h]
			output += '<path class="bthW" d="'
				+ thb
				+ '"/>\n'
	} // out_bars()

	// set the helper lines of rests
	function hl_rest(s) {
	    var	j,
		stlines = cur_sy.staves[s.st].stafflines,
		p_st = staff_tb[s.st],
		i = 5 - s.nflags,		// rest_tb index (5 = C_XFLAGS)
		x = s.x,
		y = s.y

		if (i < 6)	// no ledger line if rest smaller than minim
			return

		if (i == 7 && y == 12
		 && stlines.length <= 2)
			y -= 6			// semibreve a bit lower

		j = y / 6
		switch (i) {
		default:
			switch (stlines[j + 1]) {
			case '|':
			case '[':
			case "'":
			case ':':
				break
			default:
				set_hl(p_st, j + 1, x, -7, 7)
				break
			}
			if (i == 9) {		// longa
				y -= 6
				j--
			}
			break
		case 7:				// semibreve
			y += 6
			j++
		case 6:				// minim
			break
		}
		switch (stlines[j]) {
		case '|':
		case '[':
		case "'":
		case ':':
			break
		default:
			set_hl(p_st, j, x, -7, 7)
			break
		}
	} // hl_rest()

	// return the left x offset of a new staff
	// s is the %%staves
	function st1(st, s) {
	    var	tim = s.time

		do {			// search a voice of this staff
			s = s.ts_next
		} while (s.st != st)
		while (s.prev		// search the first symbol of this voice
		    && s.prev.time >= tim)
			s = s.prev
		if (s.bar_type)
			return s.x
		return s.x - s.wl
	} // st1()

	// ---- draw_systems() ----

	/* draw the staff, skipping the staff breaks */
	for (st = 0; st <= nstaff; st++) {
		stl[st] = cur_sy.st_print[st]		// staff at start of line
		xstaff[st] = !stl[st] ? -1 : 0;
	}
	bar_set();
	draw_lstaff(0)
	for (s = tsfirst; s; s = s.ts_next) {
		switch (s.type) {
		case C.STAVES:
			sy = s.sy
			for (st = 0; st <= nstaff; st++) {
				x = xstaff[st]
				if (x < 0) {		// no staff yet
					if (sy.st_print[st]) {
						xstaff[st] = st1(st, s)
						stl[st] = true
					}
					continue
				}
				if (sy.st_print[st]	// if not staff stop
				 && cur_sy.staves[st]
				 && sy.staves[st].stafflines ==
						cur_sy.staves[st].stafflines)
					continue
				if (s.ts_prev.bar_type) {
					x2 = s.ts_prev.x
					if (sy.staves[st].stafflines.length >
					    cur_sy.staves[st].stafflines.length)
						x2 -= s.ts_prev.wl - 4
				} else {
					x2 = (s.ts_prev.x + s.x) / 2
					xstaff[st] = -1
				}
				draw_staff(st, x, x2)
				xstaff[st] = sy.st_print[st] ? x2 : -1
			}
			cur_sy = sy;
			bar_set()
			continue
		case C.BAR:		// display the bars after the staves
			if (s.invis || !s.bar_type
			 || !cur_sy.st_print[s.st])
				break
			if (s.second
			 && (!s.ts_prev
			  || (s.ts_prev.type == C.BAR
			   && s.ts_prev.st == s.st)))
				break
			ba.push([s, bar_bot[s.st], bar_height[s.st], bar_ng[s.st]])
			break
		case C.STBRK:
			if (cur_sy.voices[s.v]
			 && cur_sy.voices[s.v].range == 0) {
				if (s.xmx > 14
				 && s.next			// if not at end of line
				 && s.next.type == C.CLEF) {	// and before a clef

					/* draw the left system if stbrk in all voices */
					var nv = 0
					for (var i = 0; i < voice_tb.length; i++) {
						if (cur_sy.voices[i]
						  && cur_sy.voices[i].range > 0)
							nv++
					}
					for (s2 = s.ts_next; s2; s2 = s2.ts_next) {
						if (s2.type != C.STBRK)
							break
						nv--
					}
					if (nv == 0)
						draw_lstaff(s.x)
				}
			}
			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)
					break
				draw_staff(st, x, x2)
				xstaff[st] = s.x
			}
			break
		case C.GRACE:
			for (s2 = s.extra; s2; s2 = s2.next)
				self.draw_hl(s2)
			break
		case C.NOTE:
			if (!s.invis)
				self.draw_hl(s)
			break
		case C.REST:
			if (s.fmr		// if full measure rest
			 || (s.rep_nb && s.rep_nb >= 0))
				center_rest(s)
			if (!s.invis)
				hl_rest(s)
			break
//		default:
//fixme:does not work for "%%staves K: M: $" */
//removed for K:/M: in empty staves
//			if (!cur_sy.st_print[st])
//				s.invis = true
//			break
		}
	}

	// draw the end of the staves
	for (st = 0; st <= nstaff; st++) {
		x = xstaff[st]
		if (x < 0 || x >= realwidth)
			continue
		draw_staff(st, x, realwidth)
	}

	// the ledger lines
	draw_all_hl()

	// and the bars
	out_bars()

	draw_vname(indent, stl)

//	set_sscale(-1)
}

/* -- draw remaining symbols when the staves are defined -- */
// (possible hook)
Abc.prototype.draw_symbols = function(p_voice) {
	var	bm = {},
		s, x, y, st;

//	bm.s2 = undefined
	for (s = p_voice.sym; s; s = s.next) {
		if (s.invis) {
			switch (s.type) {
			case C.CLEF:
				if (s.time >= staff_tb[s.st].clef.time)
					staff_tb[s.st].clef = s
				continue
			case C.KEY:
				p_voice.ckey = s
			default:
				continue
			case C.NOTE:	// (beams may start on invisible notes)
				break
			}
		}
		st = s.st
		x = s.x;
		set_color(s.color)
		switch (s.type) {
		case C.NOTE:
//--fixme: recall set_scale if different staff
			set_scale(s)
			if (s.beam_st && !s.beam_end) {
				if (self.calculate_beam(bm, s))
					draw_beams(bm)
			}
			if (!s.invis) {
				anno_start(s);
				draw_note(s, !bm.s2);
				anno_a.push(s)
			}
			if (s == bm.s2)
				bm.s2 = null
			break
		case C.REST:
			if (!gene.st_print[st])
				break
			draw_rest(s);
			break
		case C.BAR:
			break			/* drawn in draw_systems */
		case C.CLEF:
			if (s.time >= staff_tb[st].clef.time) {
				if (s.x == staff_tb[st].clef.x
				 && s.v != staff_tb[st].clef.v)
					break
				staff_tb[st].clef = s
			}
			if (!gene.st_print[st])
				break
			set_color();
			set_sscale(st);
			anno_start(s);
			y = staff_tb[st].y
			if (s.clef_name)
				xygl(x, y + s.y, s.clef_name)
			else if (!s.clef_small)
				xygl(x, y + s.y, s.clef_type + "clef")
			else
				xygl(x, y + s.y, "s" + s.clef_type + "clef")
			if (s.clef_octave) {
/*fixme:break the compatibility and avoid strange numbers*/
				if (s.clef_octave > 0) {
					y += s.ymx + 1
					if (s.clef_small)
						y -= 2
				} else {
					y += s.ymn - 2 //+2
//					if (s.clef_small)
//						y += 1
				}
				xygl(x - 2, y, (s.clef_octave == 7
						|| s.clef_octave == -7)
					? "oct" : "oct2")
			}
			anno_a.push(s)
			break
		case C.METER:
			p_voice.meter = s
			if (s.second
			 || !staff_tb[s.st].topbar)
				break
			set_color();
			set_sscale(s.st);
			anno_start(s);
			draw_meter(s);
			anno_a.push(s)
			break
		case C.KEY:
			p_voice.ckey = s
			if (s.second
			 || !staff_tb[s.st].topbar)
				break
			set_color();
			set_sscale(s.st);
			anno_start(s);
			self.draw_keysig(x, s);
			anno_a.push(s)
			break
		case C.MREST:
			draw_mrest(s)
			break
		case C.GRACE:
			set_scale(s);
			draw_gracenotes(s)
			break
		case C.SPACE:
		case C.STBRK:
			break			/* nothing */
		case C.CUSTOS:
			set_scale(s);
			draw_note(s, 0)
			break
		case C.BLOCK:			// no width
		case C.REMARK:
		case C.STAVES:
		case C.TEMPO:
			break
		default:
			error(2, s, "draw_symbols - Cannot draw symbol " + s.type)
			break
		}
	}
	set_scale(p_voice.sym);
}

/* -- draw all symbols -- */
function draw_all_sym() {
    var	p_voice, v,
	n = voice_tb.length

	// draw the slurs on 2 staves
	// sl = [symbol of 1st staff, symbol of 2nd staff, slur type, <path .../>
	function draw_sl2() {
	    var	i, a, d, dy, dy2, dy2o, dz, n, sl

		while (1) {
			sl = gene.a_sl.shift()
			if (!sl)
				break

			// extract the path header and the values
			i = sl[3].indexOf('d="M') + 4
			output += sl[3].slice(0, i)	// <path .. >d="M

			a = new Float32Array(sl[3].slice(i).match(/[\d.-]+/g))

			// update the starting point of the slur
			a[1] -= staff_tb[sl[0].st].y	// absolute vertical offset

// [0][1] = M
// [2][3] [4][5] [6][7] = c
// [8] = v			second curve if not dotted
// [9][10] [11][12] [13][14] = c
//
// y:      3-------5
//	  / 12---10 \
//	 / /       \ \
//	/ /         \ \ 7
//     1 14          \| 8
//
// x:  0   2       4    6
//       13 11    9	

			// deltas between staves, original and now
			dy2o = sl[0].fmt.sysstaffsep + 24
			dy2 = staff_tb[sl[1].st].y - staff_tb[sl[0].st].y

			switch (sl[2]) {		// slur type
			case "//":			// '~' like
			case "\\\\":

				// get the middle of the '~' slur (* 2)
				d = -(sl[1].prev.prev.y + staff_tb[sl[0].st].y
					+ sl[1].prev.next.y + staff_tb[sl[1].st].y)
					- 2 * (a[1] - posy)
				a[5] = d - a[5]
				a[7] = d - a[7]
				if (a.length > 8) {
					d = sl[2][0] == '/' ? 3 : -3
					a[8] = -a[8]
					a[10] = -a[3] + d
					a[12] = -a[5] + d
					a[14] = -a[7]
				}
				break
			case "/\\":
			case "\\/":
				d = sl[2][0] == '/'
					? dy2 - dy2o - 10
					: dy2 + dy2o + 10
				a[3] += d
				a[5] += d
				if (a.length > 8) {
					a[10] += d
					a[12] += d
				}
				break
			default:			// /+, /-, \+ or \-
				d = sl[2][0] == '/' ? dy2 - dy2o : -dy2 - dy2o
				a[5] += d
				a[7] += d
				if (a.length > 8) {
					a[12] -= d
					a[14] -= d
				}
				break
			}

			// output the slur
			output += a[0].toFixed(1) + ' ' + a[1].toFixed(1)
				+ 'c' + a[2].toFixed(1) + ' ' + a[3].toFixed(1)
				+ ' ' + a[4].toFixed(1) + ' ' + a[5].toFixed(1)
				+ ' ' + a[6].toFixed(1) + ' ' + a[7].toFixed(1)
			if (a.length > 8)
				output += 'v' + a[8].toFixed(1)
					+ 'c' + a[9].toFixed(1)
						+ ' ' + a[10].toFixed(1)
					+ ' ' + a[11].toFixed(1)
						+ ' ' + a[12].toFixed(1)
					+ ' ' + a[13].toFixed(1)
						+ ' ' + a[14].toFixed(1)
			output += '"/>\n'
		}
	} // draw_sl2()

	for (v = 0; v < n; v++) {
		p_voice = voice_tb[v]
		if (p_voice.sym
		 && p_voice.sym.x != undefined) {
			self.draw_symbols(p_voice)
			draw_all_ties(p_voice);
// no need to reset the scale as in abcm2ps
			set_color()
		}
	}

	self.draw_all_deco()
	glout()			// output the symbols
	anno_put()		// before outputting the symbol annotations

	set_sscale(-1)				/* restore the scale */

	if (gene.a_sl)		// if slurs on two staves
		draw_sl2()
}

/* -- set the tie directions for one voice -- */
function set_tie_dir(s) {
    var i, ntie, dir, sec, pit, ty, s2

	for ( ; s; s = s.next) {
		if (!s.ti1)
			continue

		sec = ntie = 0;
		pit = 128
		for (i = 0; i <= s.nhd; i++) {
			if (s.notes[i].tie_ty) {
				ntie++
				if (pit < 128
				 && s.notes[i].pit <= pit + 1)
					sec++;
				pit = s.notes[i].pit
				s2 = s.notes[i].tie_e
			}
		}

		if (s2 && s.stem * s2.stem < 0)
			dir = pit >= 22	// up if above middle staff
				? C.SL_ABOVE : C.SL_BELOW
		else if (s.multi)
			dir = s.multi > 0 ? C.SL_ABOVE : C.SL_BELOW
		else
			dir = s.stem < 0 ? C.SL_ABOVE : C.SL_BELOW

		// if other voice, set the ties in opposite direction
		if (s.multi) {
			for (i = 0; i <= s.nhd; i++) {
				ty = s.notes[i].tie_ty
				if (!((ty & 0x07) == C.SL_AUTO))
					continue
				s.notes[i].tie_ty = (ty & C.SL_DOTTED) | dir
			}
			continue
		}

		/* if one note, set the direction according to the stem */
		if (ntie <= 1) {
			for (i = 0; i <= s.nhd; i++) {
				ty = s.notes[i].tie_ty
				if (ty) {
					if ((ty & 0x07) == C.SL_AUTO)
						s.notes[i].tie_ty =
							(ty & C.SL_DOTTED) | dir
					break
				}
			}
			continue
		}
		if (!sec) {
			if (ntie & 1) {
/* in chords with an odd number of notes, the outer noteheads are paired off
 * center notes are tied according to their position in relation to the
 * center line */
				ntie = (ntie - 1) / 2;
				dir = C.SL_BELOW
				for (i = 0; i <= s.nhd; i++) {
					ty = s.notes[i].tie_ty
					if (!ty)
						continue
					if (ntie == 0) {	/* central tie */
						if (s.notes[i].pit >= 22)
							dir = C.SL_ABOVE
					}
					if ((ty & 0x07) == C.SL_AUTO)
						s.notes[i].tie_ty =
							(ty & C.SL_DOTTED) | dir
					if (ntie-- == 0)
						dir = C.SL_ABOVE
				}
				continue
			}
/* even number of notes, ties divided in opposite directions */
			ntie /= 2;
			dir = C.SL_BELOW
			for (i = 0; i <= s.nhd; i++) {
				ty = s.notes[i].tie_ty
				if (!ty)
					continue
				if ((ty & 0x07) == C.SL_AUTO)
					s.notes[i].tie_ty =
						(ty & C.SL_DOTTED) | dir
				if (--ntie == 0)
					dir = C.SL_ABOVE
			}
			continue
		}
/*fixme: treat more than one second */
/*		if (nsec == 1) {	*/
/* When a chord contains the interval of a second, tie those two notes in
 * opposition; then fill in the remaining notes of the chord accordingly */
			pit = 128
			for (i = 0; i <= s.nhd; i++) {
				if (s.notes[i].tie_ty) {
					if (pit < 128
					 && s.notes[i].pit <= pit + 1) {
						ntie = i
						break
					}
					pit = s.notes[i].pit
				}
			}
			dir = C.SL_BELOW
			for (i = 0; i <= s.nhd; i++) {
				ty = s.notes[i].tie_ty
				if (!ty)
					continue
				if (ntie == i)
					dir = C.SL_ABOVE
				if ((ty & 0x07) == C.SL_AUTO)
					s.notes[i].tie_ty =
						(ty & C.SL_DOTTED) | dir
			}
/*fixme..
			continue
		}
..*/
/* if a chord contains more than one pair of seconds, the pair farthest
 * from the center line receives the ties drawn in opposition */
	}
}

/* -- have room for the ties out of the staves -- */
function set_tie_room() {
	var p_voice, s, s2, v, dx, y, dy

	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v];
		s = p_voice.sym
		if (!s)
			continue
		s = s.next
		if (!s)
			continue
		set_tie_dir(s)
		for ( ; s; s = s.next) {
			if (!s.ti1)
				continue
			if (s.notes[0].pit < 20
			 && s.notes[0].tie_ty
			 && (s.notes[0].tie_ty & 0x07) == C.SL_BELOW)
				;
			else if (s.notes[s.nhd].pit > 24
			      && s.notes[s.nhd].tie_ty
			      && (s.notes[s.nhd].tie_ty & 0x07) == C.SL_ABOVE)
				;
			else
				continue
			s2 = s.next
			while (s2 && s2.type != C.NOTE)
				s2 = s2.next
			if (s2) {
				if (s2.st != s.st)
					continue
				dx = s2.x - s.x - 10
			} else {
				dx = realwidth - s.x - 10
			}
			if (dx < 100)
				dy = 9
			else if (dx < 300)
				dy = 12
			else
				dy = 16
			if (s.notes[s.nhd].pit > 24) {
				y = 3 * (s.notes[s.nhd].pit - 18) + dy
				if (s.ymx < y)
					s.ymx = y
				if (s2 && s2.ymx < y)
					s2.ymx = y;
				y_set(s.st, true, s.x + 5, dx, y)
			}
			if (s.notes[0].pit < 20) {
				y = 3 * (s.notes[0].pit - 18) - dy
				if (s.ymn > y)
					s.ymn = y
				if (s2 && s2.ymn > y)
					s2.ymn = y;
				y_set(s.st, false, s.x + 5, dx, y)
			}
		}
	}
}
// abc2svg music font
var musicfont = 'url("data:application/octet-stream;base64,\
AAEAAAAOAIAAAwBgRkZUTZjuVTkAAFesAAAAHEdERUYAFQAUAABXkAAAABxPUy8yWLldDAAAAWgA\
AABWY21hcI+tzq4AAAQEAAAD1mN2dCAAIgKIAAAH3AAAAARnYXNw//8AAwAAV4gAAAAIZ2x5Zjpw\
qQYAAAkEAABFtGhlYWQZC66+AAAA7AAAADZoaGVhCWn/GgAAASQAAAAkaG10eNmK+y4AAAHAAAAC\
RGxvY2GUUqVMAAAH4AAAASRtYXhwANgBEgAAAUgAAAAgbmFtZSB54KwAAE64AAADIXBvc3Rcker6\
AABR3AAABasAAQAAAAEAALjJexZfDzz1AAsEAAAAAADRlyIXAAAAAOPSSjP/OPzvBUsEiAAAAAgA\
AgAAAAAAAAABAAAEiPzvAFwEJf84/XQFSwABAAAAAAAAAAAAAAAAAAAAkQABAAAAkQDhAAUAAAAA\
AAIAAAABAAEAAABAAC4AAAAAAAEBggGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAACAAUDAAAA\
AAAAAAAAARAAAAAAAAAAAAAAAFBmRWQAQAAA7LcDOP84AFwEiAMRAAAAAQAAAAAAAAF2ACIAAAAA\
AVUAAAGQAAACWAAAAFcAAAAjAAAAJQAAACT//wBkAAAAZAAABCMAAAQlAAAB4P/cA7oAAAMLAAAC\
0gAAAr//ugHWAAADCwAAAw4AAAMn/8gAyAAAAWgAAAGuAAABIgAAAZAAAAF8AAABkAAAAZAAAAGB\
AAABkAAAAZAAAAGBAAABnwAAAZ///wH0AAABBAAUAQQACgJrACQCEgAAAcIAAAFCAAABQAAAAUr/\
/gEsAAACMAAAAUoAAAFKAAAAZAAAAUAAAAFAAAABQAAAAUAAAABkAAABNgAAAOYAAAE2AAABOwAA\
ATsAAAE7AAABOwAAATsAAAE7AAABOwAAATsAAAE7AAABOwAAAQ0AAADIAAAA/wAAAQsAFAFuAAAA\
jAAAAIwAAAENADIBbv/1AKkAAAE6AAABQP/9AFAAAAFUAAAAZAAAARgAAAJYAAAAtgAAAZAABQCC\
AAAAggAAASwAAAEsAAAA7gAAAP8AAAFJAAABjwAAAdgAAAHYAAACM//wAyD/4QF7/7QBuP/bARb/\
fgET/9sA3AAAAOj/5AK//7QCM/+0Ar//tAMr/9sBX//bAmn/fgFf/34Caf9+AV8AAAH9AAUBtQAA\
AbUAAAJEAA0CRAANARgAAAE2AAABLP//ASwAAAD6AAAAyAAAARj/OAD6AAAAyAAABA0AAAIcAAwB\
9AAAAfQAAAH0AAAB9AAAAfQAAAH0AAACHAAAAPoAAAD6/+gBwgAAAUgAAAFAAAACCgAAAgoAAABk\
AAAAAAADAAAAAwAAABwAAQAAAAAC0AADAAEAAAAcAAQCtAAAAFwAQAAFABwAAAAg4ADgMOA54Ejg\
UOBc4GLgaeCM4JXgpOCp4LPhAeG74efiAOJJ4mTia+KD5KzkwOTR5OrlAeUx5TnlbeWC5dDl4uYY\
5iTmMOZQ5lXpGOld6gLqpOyp7Lf//wAAAAAAIOAA4DDgOOBD4FDgXOBi4GngeuCU4KDgqeCz4QHh\
ueHn4fLiQOJg4mrigOSg5MDkzuTh5QDlIOU55WblguXQ5eLmEOYk5jDmUOZV6RDpXeoC6qTsouy3\
//8AA//kIAUf1gAAAAAfvx+0H68fqQAAAAAAAB+DH3ofLQAAHkoAAAAAAAAAAAAAAAAblAAAAAAA\
AAAAGzgAABr1GqgalwAAGloaTxowGiwAABcrFocV5gAAE9kAAQAAAAAAAAAAAFQAVgAAAAAAAAAA\
AFgAfAB+AAAAAAAAAIAAAACCAJ4AsAC4ALoAwAAAANYA3ADuAPAAAAEQAAAAAAAAARgAAAAAAAAA\
AAEgAAAAAAAAASoAAAAAAAcACAAJAAoACwAMAA0ADgATABQAFQAWABcAAAAYABkAGgAbABwAHQAe\
AB8AIAAhACIAIwAkACUAJgAnACgAKQAqACsALwAAADAAMgAAADMAAAAAADQAAAA1AAAAAAA2AAAA\
NwA4ADkAOgA7ADwAPQA+AD8AQABBAEIAQwBEAEUARgBHAEgASQBKAEsATABNAE4ATwAAAFAAAABR\
AAAAAAAAAFIAAAAAAAAAUwBVAAAAAABWAFcAWABZAFoAWwBcAF0AXgBfAGAAYQBiAGMAZABlAGYA\
ZwBoAAAAAAAAAGkAagBrAGwAbQAAAG4AbwBwAHIAcwAAAHQAAAAAAHUAdgB6AAAAewAAAHwAAAAA\
AAAAfQCCAIMAhAAAAIUAhgAAAAAAhwCLAIwAAACNAAAAjgAAAI8AAAEGAAADAAAAAAAAAAECAAAA\
AgAAAAAAAAAAAAAAAAAAAAEAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAAAAAAAAIgKIAAAAKgAqACoANgA+AG4AegCGAJIAsADCATYBkgICAkYC0gNk\
A8QD2AR2BQgFYAWgBiIGRAZYBqoG+gcaB2AHoAfaCCYIZgieCO4JAgkoCU4JgAmcCb4J5gn8ChYK\
TApaCmYKcgqECqAKwgrQCuQK9gsGC1ILYgt8C5YLzgwGDFQMoA0GDWgN5g5eDoQOng7ODvgPUA9q\
D4QPqhAEECQQYhCKEJ4QqhC4EMgQ8BEYEVARXBFoEXQRgBGiEcgR/hJMEqwTIBNGE3QTyBQ8FJAU\
0BUUFWoWhBdcF/IYpBlmGfgayhveHModDB1QHZodtB3YHeweAh4yHkIeVB5wHoYeqh7aH7YgciCc\
ILog6iEoIVohnCG4IdIh+CIaIkgiYiKMIsgi2gACACIAAAEyAqoAAwAHAC6xAQAvPLIHBADtMrEG\
Bdw8sgMCAO0yALEDAC88sgUEAO0ysgcGAfw8sgECAO0yMxEhESczESMiARDuzMwCqv1WIgJmAAAB\
AAAAAAGRAZAAAwAAMREhEQGRAZD+cAABAAAAAAAAAAAAAAAAMQAAAQAAAAAAVwQDACAAABE1NjU0\
JyY1NDcGFRQXFhUUBxYVFAcGFRQXJjU0NzY1NDUjElc/FSVNTSUVP1cSIwIDAhhDNmA5NGY6Mksi\
OWFNYRgYZkxgOSVKMjpmNDlgNkgAAQAAAAAAIwPoAAMAABEzESMjIwPo/BgAAQAAAfQAJQPoAAMA\
ABEzESMlJQPo/gwAAf//A2QAJARgAAMAABEzByMkASQEYPwAAgAAAUAAZAKeAAcADwAAEiImNDYy\
FhQCIiY0NjIWFEcqHR0qHR0qHR0qHQI6HSodHSr+6R0qHR0qAAAAAQAA/84AZAAyAAcAABYiJjQ2\
MhYURyodHSodMh0qHR0qAAAABQAAAAAEJAGuAC8ANwA/AEcAUwAAITUzHgEzMjY1NCcuBDU0NjMy\
Fhc3MxcjLgEjIgYVFB4DFx4BFRQGIyInByAiJjQ2MhYUBCImNDYyFhQBETMyNjQmIwM1MxEjNTMy\
FhUUIwJOHhVPMik7lBkaKhYRWT0kJxkeHgceD0owHzkQIhkyCE5NW09FLiMBmyodHSod/eoqHR0q\
Hf6JKDxGRjzcRkbccYn6oDxLICEtKAcIFBQjFUNNCw4ZmzpIKBsPFw8JCwIVNzM6TiAgHSodHSod\
HSodHSoBaf6YYaZh/noeAWgeZ2vSAAUAAAAABCQBrgAaACIAKgAyAD4AACEiJjU0NjMyFhc3MxcH\
JiMiBhQWMzI2NxcOATIiJjQ2MhYUBCImNDYyFhQBETMyNjQmIwM1MxEjNTMyFhUUIwMCZ3WCWiUp\
Gx4eCCEkXjg2Njg2TREjFFmxKh0dKh396iodHSod/okoPEZGPNxGRtxxifprZ2V3DBIepgSMbZht\
ST4KSlEdKh0dKh0dKh0dKgFp/phhpmH+eh4BaB5na9IAAAAD/9wAAgHeArMABwAPAE0AAAAiJjQ2\
MhYUBCImNDYyFhQXNDYzMhYVFAcWMzI2NTQvAQMnEy4BNTQ+ATc2MzIWFRQGIyImNTQ3JiMiBhUU\
HwETFwMeARUUDgEHBiMiJgGeIBgYIBj+fiAYGCAYWxsUEx4sFykmNiZ7zyvRWkgcExQmMzA4GxQT\
HiwXKSY2JnrUK9VaSBwTFCYzMDgBUhggGBggVBggGBgg0xIcGhEdDhctJi0mZf7eIAElR3E4Fi0R\
ERM4IRIcGhEdDhctJi0mZQEmH/7XR3I4Fi0RERM4AAUAAP9WA7wDYgAXABsAHwAjACcAAAEzFR4B\
FzMVIw4BBxUjNS4BJyM1Mz4BNxEjFhcTETY3JzMmJwMRBgcBxDRwnwmsrAmfcDRwnwmsrAmecZQH\
jTSOBpSUBo40jgYDYqsLtoA0f7cLq6sLt380grQL/ov0FAEI/vgU9DT0FP74AQoU9gAAAAAEAAD9\
bwKnBIgACwBEAE4AYwAAAQYVFBc+ATU0Jw4BExcVFAYjIiY1NDYzMhYUBgcWMzI2PQEnBiMiJjU0\
Nz4GPwEmNTQ2NxYVFAYHFzYzMhYVFCc0JiMiBiMTPgEnDgEVFBYXLgE1NDcnDgEHHgEzMjcBbAcF\
SHU2OUJLF09NUl9AMi9BPy8tGC9AFxwNmNs6DSQgLh0xEhcWDWxJYlx5FBQLcYhOYlACBwIhW0Hi\
NkYnHT0/phGPbAECoXkKFgNTNy0jWjGVSHINB177bOwDTFxTQS1IO1g3ARk8RgPpAsedhWYXMCUt\
GicOERCWZoyhBzrliKlfzwKcc866PmYB/p0SXfILRjEgQhIORUepLr1yl2eNowIAAgAA/fwC0gIA\
AGQAaAAAATI2NTQnJiMiBw4CByYnJicRIxEzETY3NjceAxcWMzI2NTQnJiMiBxYXFBYVFAYrASY1\
NDc2NzYzMhcWFxUUBgcGIyInBxc2MzIWFxYdAQYHBiMiJjU0NzMyFhUUBhUGBxYBMxEjAd4+Sg0a\
SUY8AgYKBCIaHi4cHC4eGiIGFAwZDyclMT0SJFIvMTINAjMhBUQFGlUnI15VNwhaSB8tND8iIj80\
QmYeKAg3Vl1MckQFITMCDzA1/k17e/4geEotLWxJBQ4bCmInKx/+BAQA/hEfKydiCyoXHQkbe0Ix\
NmIaECgDDQQeKxkyFAtEGQ1PNFMSTm8cDRdLShc+LDlDElM0UE48MhkrHgQOBCYQHAPe/AAAAAP/\
uv2lAsMA/wAqADYAQgAANzQ2MzIWFxYVFAYHDgEHNjc+ATc2NTQmJy4BIyIGBz4BMzIWFRQHBiMi\
JgUiJjU0NjMyFhUUBiciJjU0NjMyFhUUBhOLZ1VrKy9GVWbPleWFMTQTChEdHDQzO2EWGCMbLj0n\
ITEzRQKCFh0aFBUeGhoXGxwVFBwbHWKANDk+cn+6T2BNCD2FMWNQK2ZHUCMiF09GHhdBLzIgHlGK\
HBcWHB0VFh3yHhkVGhsUGR4AAAIAAP8GAXIA+gADAAcAADczEyMDMxMj3JQCltyUApb6/gwB9P4M\
AAAEAAD+CgIfA6oACQAgAGMAbgAAJRYXPgE1NCYjIgMCJw4BFRQXLgE1NDY3JicOAQceATMyFx4B\
HwEwHQEUIyImNTQ2MzIWFRQGBxYzMjY1NC8BBiMiJjU0Nz4BNz4CNyY1NDY3HgEVFAYHHgEXNjMy\
FxYVFAcGAwYVFBc+ATU0JwYBSRMGTUdWQg4NGAEsOR8gKkw7BQlxVQEFbYIDIgMGAgJ6NlMzKCU1\
MScVIiMsAQ0JFY2ZLg5MHgUjJxIOWUAvGUlhAggEEghcOTJjNmQDBjdeKV91xFsSTzM2Vv7gAQsQ\
CTQnKSYSQSk4ThA+Wlp5U3F+GiBAFxcbB39IMSQzNyQiKAEMMzUPCY0BkopqURxQGAQgIQ3CB25/\
EzNiW22HTBFuJAJDNmJ3MBsDVh4iOSQlezNGJiYAAAAAAgAA/mMCQgGaAGMAZwAAATI2NTQnJiMi\
Bw4BByYnJicRIxEzETY3NjceAxcWMzI2NTQnJiMiBxYXFBYVFAYrASY1NDc2NzYzMhcWFxUUBgcG\
IyInBxc2MzIWFxYdAQYHBiMiJjU0NzMyFhUUBhUGBxYBMxEjAX4yOwoVOjgwAgwEHRMYJRYWJRgT\
HQQRChMMIB0nMQ4cQyUnKAoCKhoENgQVRCMYSUYtBkg6GCUrMRsbNCg1UhggBi1GST1bNgQaKgIM\
Jiv+o2Ji/oBgOyQkVzsFHQtSHCIZ/moDM/50GSIcUggiEhcIFmI1KyhOFQ0gAgsDGCIUKBAJNRUL\
QCtBDj5aFgoSPDsTMiMuNQ9BK0A/MCgUIxgDCwMfDRYDGPzNAAAAAAP/yP4eAjYAzAAmAC8AOwAA\
NzQ2MzIXFhUUBw4BBzY3Njc2NTQnLgEjIgYHPgEzMhYVFAcGIyImBSImNDYyFhQGJyImNTQ2MzIW\
FRQGD3BSgjwjeEPGacBeRx4JLRgtIDBSChIWFiU5IBklLDkCAhIXFCIYFRUSFhUSERYVIU1eVzNa\
xmw7VwYzZEqMLylmMxoUQjYXDTwmKBoYU3UXJBYXIhjCGBQRFBUQExkAAAAAAwAAAAAAyADwAAkA\
FQArAAA3BhUUFjMyNjU0JyIGFRQeARc2NTQmByImNTQ2Ny4BNTQ2MzIWFRQHFhUUBlYsHhARFAIO\
EwoJECUUNCU1IyIRDCgeKTE3IzR0EB4UHhsRF4URDgoQCAwLHQ4X3CgeFxoLDxMQGiIfHSUOHB8g\
JgACAAD//wFnAQMAMABiAAAXIjU0MzIVFAYVFDMyNjU0IyIHBiMiNTQ3NjM2MzIWFRQHBgcOAQcG\
Fjc2MzIWFRQGJyI0OwEyNzI0MzY3PgE1NiMiBwYnJjc+ATc2OwIyFxYzMjc2BwYHBhUUMzIVFCsC\
1D4YGxIdFyweChQWCAsmBgkjSQ8FFhpICgYBAgUIDA4gI0P6CQkJFAUBAQ0XAQIIDgkIDA0KDwYR\
AjIEAgMGDgwIAggXBkcKAhUMDCcnATchGwcWBg5CIxwMDQ8WVAsBAgUQBwkQAgYLCgUCAyQgKDkB\
EgwEHz0BBQIVCQ4GBhIHGAJABwYCCBKtHQUFCAoIAAAAAAIAAP8GAa4A+gALABQAADMUFjMyNjU0\
JiMiBgc0NjIWFAYiJooqIyIrJyYlKIp9tH19tH1ieHlhZXV2YWeQkdKRkgAAAQAA/wYBIgD6AAkA\
ADE3MxEXFSM1NxFkfUHwQfr+Ph4UFB4BLAAAAAEAAP8GAY8A+gA8AAA3MhUUBw4DBzYzMhYzMjc+\
AjMOAgcGBwYjIiYjIgYjIjU0Jz4FNTQnIgcyFhUUBiMiNTQ+AcfIBQ02QG82EyAbZBwYHgUQDAEB\
BQUBBxAaKRp0FR9WAgcBAiw+RzwoU04aHCk3Hkw8WPp+Gg4hLh1ELQwjDgMNCwUWFgMpDhgnJhAB\
AiFFODwyOBhiATUlHh8pZyg5GQAAAQAA/wYBdQD6ADkAADcyFhUUBiMiJjU0NzYzMhcWFRQGBx4B\
FRQHBiMiJicmNDYzMhYVFAYjFjMyNjU0JicmNDc+ATQmIyJmGyInIRsyHzNZRiZERj0+UUskTSdX\
GCMyICIqJRsMPyQrSC4WFi9LKSQ8qhwXGyMrIy8aKhMiSC5ECwtFLUMnExYUHUwuIRsZHikxJyY6\
CAQiBAk3UDAAAAEAAP8GAZAA+gARAAAFFyM3NSM1NjUzATM/AREzFSMBRTLIMuGTo/77sAFjS0vR\
KSkxKPKA/o6Wkf7ZKAAAAAABAAD/BwF+APoALwAAFzYzMhYVFAYjFjMyNz4BNTQnJiMiBxMhDgEr\
AQc2MzIXHgEVFAcOAiMiJy4BNTQSHCEbKiAcGiQxHBMJHhwoTkgKAWILNSXVBjlCUzEhK0MWQCsl\
PCsQHl8gIBccICEeFB8gORwaNQEiJDp5Hh8VQSVPLxAQAhQJMhIjAAAAAAIAAP8GAYEA+gAJACwA\
ABcyNjU0JiMiBxYTFhUUBiMiJjU0NjMmIyIGFT4CMzIWFRQGIyImJz4BMzIWyCktKigsMAfdGyMY\
HiIbEBY3NS8VGC0eTE9xSGFmAQFsWzA/0kUsIjAlngGfGiYZKB4bDB4jeF8LCglANkRZgnhpkRIA\
AQAA/wYBkAD7ACgAADciDgMHNz4IMzIWMzI2Nw4EFSM2NzY3NjcGIyImYRQaFAkRBQoBDAILBQsJ\
DRAJL3UjGjsRG0UaHgiCAQgRaB4tER4lYKEFDgkbBnQBCwIJAQYBAwEmFw5Dp0FcQitFGzaHJzkK\
KAADAAD/BgGEAPoADgAcADQAABcOARUUFjMyNjU0LgM3PgE1NCYiBhUUHgMHLgE1NDY3MhYVFAYH\
HgEVFAYjIiY1NDaaNixYLCo/DyAdMT4zI0RSMwocEjFwMSlmSktlKjA6MnVNTHY5KhkkGx0wKR8O\
FxINE1oaIhwdMCggDxcTChU/GDw1M00BRjInNBcaOjU3SkgwJDUAAAIAAP8GAYEA+gAJACwAADci\
BhUUFjMyNyYDJjU0NjMyFhUUBiMWMzI2NQ4CIyImNTQ2MzIWFw4BIyImuSktKigsMAfdGyMYHiIb\
EBY3NS8VGC0eTE9xSGFmAQFsWzA/0kUsIjAlnv5hGiYZKB4bDB4jeF8LCglANkRZgnhpkRIAAQAA\
/wcBnwD+ACcAABcyNxcOASMiJjU0PgIzMhYVFAYjIiY1NDY3MjU0JiMiBhUUHgPveh4YEGVNWoMo\
QU4pRGInHSAtJSgKOh8zThQbJRvZngVqT49qQGU7Hks+JDMuHBksAQYLIXBqOlYtGwcAAAL///6i\
AZ8BXgAzADkAABcWMzI3NjcUFhUOASsBByM0Iy4BNTQ2NzUzFTMyFhUUBiMiJjU+ATsBMh4BMzY1\
NCYjIg8BBhUUFxDbEgI9JC0KGAprSwMBLQFSXFtULQhOVScdIC0CJBoBBAYGAgVFHgQMKz081gMn\
MEcBAgJfWmVrFI9XVokSZmJOOSQzLhwWJgECBwYRIQIWNI6RNgGJAAABAAD/BgH0APoACwAANTM1\
MxUzFSMVIzUj10bX10bXI9fXRtfXAAAAAQAU/gYA5AIAABMAABMWBwYnJgI1NBI3NhcWBwYCFRQS\
3AcNCQVJa2tJCQsHBzxGR/4WCAUDBlcBIH18ASJWCwcFCUn+54iG/uUAAAEACv4CANwB/QATAAAT\
NhI1NAInJjc2FxYSFRQCBwYnJhQ7R0Y8CQwKB0lra0kICwb+FkkBG4aIARlJCwQECVb+3nx9/uBX\
CQkEAAAEACT/VgJMAKoACwAPABMAHgAABTQmIyIGFRQWMzI2NzMRIwEzESMkFAYjIiY1NDYzMgGo\
VDwgNFc9IS9yMjL+CjIyAfV7ZmV8eWhmIDVOJx81SyPq/qwBVP6s5nhGSTk/QwAAAgAA/yQCEgDc\
AAMADwAANxUhNSUzFSE1MxEjNSEVIx4B1v4MHgHWHh7+Kh5BgoKbNzf+SDc3AAACAAD/fwHCAIEA\
CwATAAAFNCYjIgYVFBYzMj4BFAYiJjQ2MgFRWjYgNF03IS9xfch9fcgeNE8nHzRMI3RsS0tsSwAA\
AAIAAP92AUIAigAMABgAACU0JiMiBhUUHgEzMjYnMhYVFAYjIiY1NDYBIxocO5MEGxc7k2FAQHtH\
QEB7Lg4ZWygEDhVbhEwpP2BMKT9gAAAAAQAA/3kBQACHAAsAACUUBiMiJjU0NjMyFgFAeVkyPHpY\
MjwoRmk4J0VqOAAB//7/bwFMAJEACwAAJzcXNxcHFwcnByc3AhuMjBuGhhyLixyGcSB2dSBwcCF1\
dSFwAAAABQAA/2oBLACWAAUACwARABcAHwAAFwcWMzI3LwEGFRQXPwEmIyIHHwE2NTQnBjQ2MhYU\
BiKWRx0qKR9dRx0dXEgfKSodXEcdHfJYfFhYfBJIHR1aSR8pKh1YSB0dWkgfKSodhXxYWHxYAAAA\
AQAA/wYCMAD6AAMAABUBMwEBuHj+R/oB9P4MAAEAAP90AUoAjAADAAAxNxcHpaWljIyMAAEAAP90\
AUoAjAACAAAVGwGlpYwBGP7oAAEAAP/OAGQAMgAHAAAWIiY0NjIWFEcqHR0qHTIdKh0dKgAAAAEA\
AP95AUACqAAPAAARIREUBiMiJjU0NjMyFxEhAUB5WTI8elgxH/7eAqj9gEZpOCdFahwBxQAAAQAA\
/3kBQAKoABMAABEhERQGIyImNTQ2MzIXESE1ITUhAUB5WTI8elgxH/7eASL+3gKo/YBGaTgnRWoc\
ARF4PAAAAAABAAACMAFAAqgAAwAAESEVIQFA/sACqHgAAAAAAgAAAXwBQAKoAAMABwAAESEVIREh\
FSEBQP7AAUD+wAH0eAEseAAAAAEAAP/aAGQAPgAHAAAWIiY0NjIWFEcqHR0qHSYdKh0dKgAAAAEA\
AAMCATYDwAAFAAARIRUhFSMBNv7oHgPAHqAAAAAAAQAAAyoA5gQ4ADkAABMiJjU0NjMyFhUUDwEU\
MzI2NTQmKwEiNTQ7ATI2NTQmIyIVFBYVFCMiJjU0NjMyFhUUDgEVFBYVFAZQJykQDg8SCwwhFSQR\
DxIUFCgXJQ4OFQIhDBIlKyMtIyMUMwMqHhgOFhAMEgcHDSofDxkOECUVDRMNAwgIHBEMFR4iGhgj\
EwIBIg8jLQAAAAABAAADAgE2A8AABQAAARUjNSE1ATYe/ugDwL6gHgAAAAEAAPzvATsAAAAPAAAV\
NTMeBBUUBzY1NCYnHgY/UVA3LhKRcO/vNXBlbIlJYGlBSY/fKgABAAAAAAE7AxEADwAAMTUzPgE1\
NCcWFRQOAwcecJITLjdQUT8G7x/hk0dIZ15Iim1ncTUAAgAA/UQBPAAAABgAJgAAGQEzHgYVFAcW\
FRQHNjU0LgMjNR4DFzQ2NTQuAx4GJzM7OC4cEhMeBSk+SEAWCURNVhUBKT5IQf6pAVcbOTM3Oj1I\
JSwrKSs2ORkhN2RFNBirJFRDXSkEDAQ3ZUUzGQAAAAIAAP//ATwCvAAYACYAABURMzI+AzU0JxYV\
FAcWFRQOBQc1Mj4DNTQmNQ4DHhZASD4pBR4TEhwuODszJwYWQUg+KQEVVk1EAQFXGDRFZDchGTk2\
KykrLCVIPTo3MzkbqxkzRWU3BAwDKV1DUwAAAAADAAD9KgE8AJEAGwApADcAABkBMx4GFRQHFhUU\
BxYVFAc2NTQuAiM1HgMXNDY1NC4CIyceAxc0NjU0LgIjHgYnMzs4LhwSEhITHgU9V1UcCURNVhUB\
PFZVHQIJRE1WFQE8VlUd/pACARs5Mzc6PUglLCsnLSwrKSs2ORkhRHdIKakkVEFdKQQNA0R2SCmr\
JFNCXSkEDQNEdkgpAAADAAD/VgE8Ar0AHAApADYAABEzMj4DNTQnFhUUBxYVFAcWFRQOBQcjNzI+\
AjU0JjUOAycyPgI1NCY1DgMeFkBIPikFHhMSEhIcLjg7MycGHh4dVVc9ARVWTUQJHVVXPQEVVk1E\
AVcYNEVkNyEZOTYrKSssLScrLCVIPTo3MzkbqilJeEQEDAQpXUNUhilJeEQDDQQpXUNUAAQAAP1C\
ATwBVAAgAC4APABKAAAZATMeBhUUBxYVFAcWFRQHFhUUBzY1NC4DIzUeAxc0NjU0LgMnHgMXNDY1\
NC4DJx4DFzQ2NTQuAx4GJzM7OC4cEhISEhITHgUpPkhAFglETVYVASk+SEEWCURNVhUBKT5IQRYJ\
RE1WFQEpPkhB/qgCrBs5Mzc6PUglLCsnLSwrJy0sKykrNjkZITdkRTQYqyRUQ10pBAwEN2VFMxmr\
JFRDXSkDDgM3ZUUzGaskVENdKQQMBDdlRTMZAAQAAP6OATwCoAAfACwAOQBGAAAZATMyPgI1NCcW\
FRQHFhUUBxYVFAcWFRQOBQc1Mj4CNTQmNQ4DJzI+AjU0JjUOAycyPgI1NCY1DgMeHFVXPQUeExIS\
EhISHC44OzMnBh1VVz0BFVZNRAkdVVc9ARVWTUQJHVVXPQEVVk1E/o4CrClId0QhGTk2KykrLC0n\
KywtJyssJUg9OjczORuqKUl4RAMNBCldQ1SGKUl4RAMNBCldQ1SGKUl4RAMNBCldQ1QAAAAFAAD9\
VQE8AhIAJAAyAEAATgBcAAAZATMeBhUUBxYVFAcWFRQHFhUUBxYVFAc2NTQuAyM1HgMXNDY1NC4D\
Jx4DFzQ2NTQuAyceAxc0NjU0LgMnHgMXNDY1NC4DHgYnMzs4LhwSEhISEhISEx4FKT5IQBYJRE1W\
FQEpPkhBFglETVYVASk+SEEWCURNVhUBKT5IQRYJRE1WFQEpPkhB/rsDVxs5Mzc6PUglLCsnLSwr\
Jy0sKyctLCspKzY5GSE3ZEU0GKskVENdKQMNBDdlRTMZqyRUQ10pBAwEN2VFMxmrJFRDXSkDDgM3\
ZUUzGaskVENdKQQMBDdlRTMZAAAFAAD9vAE8AnkAIwAwAD0ASgBXAAAZATMyPgI1NCcWFRQHFhUU\
BxYVFAcWFRQHFhUUDgUHNTI+AjU0JjUOAycyPgI1NCY1DgMnMj4CNTQmNQ4DJzI+AjU0JjUOAx4c\
VVc9BR4TEhISEhISEhwuODszJwYdVVc9ARVWTUQJHVVXPQEVVk1ECR1VVz0BFVZNRAkdVVc9ARVW\
TUT9vANXKUh3RCEZOTYrKSssLScrLC0nKywtJyssJUg9OjczORuqKUl4RAMNBCldQ1SGKUl4RAMN\
BCldQ1SGKUl4RAMNBCldQ1SGKUl4RAMNBCldQ1QAAAACAAD/ZADhAbAACgAWAAA3IgYdATY3NjU0\
JjcyFhUUBwYjETMRNmcUKyImKx0HIzlLUkQoI3omE7kPODsvGyYmMiNJTFICTP68NAAAAgAA/oYA\
xQF6AAMADAAAFzc1BxEVNxEjNQcRNxyQkKkZrAFlLZYtAUnoNP3A4jMCQwEAAAIAAP6YAP8BaAAD\
AB8AADcVNzUDIzUHNTc1BzU3NTMVNzUzFTcVBxU3FQcVIzUHU1paHjU1NTUeWh01NTU1HVpGpxun\
/jejD1wPpw9aD6ifHKujD1wPpw9aD6ifHAAAAAEAFP+EAQsAegAeAAAXNSYnBzAVIzUzNycwIzUz\
FRYXNzA1MxUjBgcXMDMVwygMM0g5MzM5SCESNEg5IRM0OXw7Jg0zO0oyMkg5IhEzOUciETRIAAQA\
AP9qAWwBsAAOABwAKwA6AAA3DgEdATI3Njc2NTQnJiM3MhYVFAcGBwYjETMRNhcOAR0BMjc2NzY1\
NCcmIzcyFhUUBwYHDgEjETMRNk4RHg4eHwwEChARGR0rCRgrNS8fGdERHRAeHQsGCxAPFh8qCxko\
FjcWHht9AR4QxikrNA0ZHhQVJjkhEiA5NEACRv7BMiYBHRHGKS8wExMcFhUmNiQWHD4vGyUCRv7B\
MgAAAQAA/wYAjAD6AA4AADcVBhUUFxUuAzQ+AoxQUBolMhsbMiX6FEOjpkAUDx86WnBaOh8AAAEA\
AP8GAIwA+gAOAAA1HgMUDgIHNTY1NCcaJTIbGzIlGlBQ+g8fOlpwWjofDxRApqNDAAACADL/ZAEN\
AbAACgAWAAA3IgYVFBcWFzU0JicyFxEzESInJjU0NqsXIC4uGiozOiMjOlJPOXomGzBFQgPLEh4m\
NAFE/bRSUEUjMgAABP/1/2oBbAGwAA4AHQAsADsAADcjIgcGFRQXFhcWMzU0JicyFxEzESImJyYn\
JjU0NhcOAR0BMjc2NzY1NCcmIzcyFhUUBwYHDgEjETMRNlcBERAKBAwfHg4eKzAZHxY3FysYCSvp\
ER0QHh0LBgsQDxYfKgsZKBY3Fh4bfRUUHhkNNCspxhAeJzIBP/26JRs0OSASITkmAR0RxikvMBMT\
HBYVJjYkFhw+LxslAkb+wTIAAAAAAQAA/sAAqQFAABMAABMzFTcVBxU3FQcVIzUHNTc1BzU3RB5H\
R0dHHkREREQBQKIOXA5/DloPqKIOXA5/DloPAAMAAP6YAToBaAAjACcAKwAANzUzFTcVBxU3FQcV\
IzUHFSM1DwEjNQc1NzUHNTc1MxU3NTMVAzUHFTcVNzXpHjMzMzMePR48AR4zMzMzHj0eHj1bPcCo\
ng9cD58PWg+1qhKspBCong9cD58PWg+1qhKspP79nhGfuJ4RnwAB//0AAAE/APQAGAAANwYjIiY1\
ND8BNi8BJjU0NjMyMRcFFhUUBxICAwcJBs8ODs0ICwcBAgEfDg4BARAICgNJBwZPAwsKEgFrBg4N\
BQAAAAEAAAAAAFAAUAAJAAA1NDYyFhQGIyImFyIXFxEQGCgRFxciFxgAAAABAAAAAAFUADIAAwAA\
MTUhFQFUMjIAAAABAAAAAABkARgAAwAAMwMzAygoZCgBGP7oAAAAAQAAAAABGAE1AAUAADEbASMn\
B4yMQVhaATX+y8bGAAACAAAAAAJYAUoADgAZAAAxNDYzMh4CFSMuASIGByEiJjQ2MzIWFRQGs3k5\
a1UzDwui4KILARwXJSUXGSMjmLIsUYBNboaGbiQwJCQYGSMAAAEAAAAAALYBLQAYAAATMhYXFhUU\
Bw4BIzAnJjU0NjU0Iy4BNTQ2VhsbEBoyGUQQBgFHFBsoLQEtDBEdMD08HS0DAQIIaxMPASYcHjEA\
AAAAAgAFAAABjgH1ABEAIwAANxM2MzIXFhUUBwMGIyInJjU0JxM2MzIXFhUUBwMGIyInJjU0mM0L\
CgYDCwXMCQsEBguLzQoLBgMLBcwIDAQGCyMBwBICBQoGDP4/EQMGDAcHAcASAgUKBgz+PxEDBgwH\
AAEAAP8GAIIA+gADAAA1MxEjgoL6/gwAAAEAAAAAAIIA+gADAAA1MxUjgoL6+gAAAAEAAP+DASwA\
AAADAAAxIRUhASz+1H0AAAEAAAAAASwAfQADAAA1IRUhASz+1H19AAEAAP5+AOsBhwATAAATFwcX\
JiMiBhUUFyY1NDYzMhcnNym9Z2wyNB8mOHg0JSIih2QBh+XZzy4kHTU0S00jLRW8tAAAAQAA/w0B\
AADAABYAADcOAiMiJjU0NjIWFRQHMjY3NjIXAyerAxkaEys3JjgpFyIzIQIVA5YwPAEHBCkoHyAe\
GR0bISwCAv5vEAAAAAEAAP4MAUgAwAAkAAAXBiMiJjU0NjMyFhUUBzI/AQYjIiY1NDYzMhYVFAcy\
NzYyFwMnqyghKzcnGxwpF0ELPDYYKzcnGxwpF0guAhUDxS3EDCgoICAfGR0bIsoMKSgfIB4ZHRtN\
AgL9bgwAAAEAAP4MAY8BwAA2AAA3BiMiJjU0NjMyFhUUBzI/ASIOASMiJjU0NjMyFhUUBzI3NjIX\
AScTBiMiJjU0NjMyFhUUBzI39igfKzcnGxwpFz8LOgEgHBMrNycbHCkXSC4BFgP+9C1VKCErNycb\
HCkXQQs8DCgoICAfGR0bIssJBCkoHyAeGR0bTQIC/G4MASQMKCggIB8ZHRsiAAAAAAEAAP0MAdoB\
wABFAAATBiMiJjU0NjMyFhUUBzI/AQYjIiY1NDYzMhYVFAcyPwEGIyImNTQ2MzIWFRQHMj8BIg4B\
IyImNTQ2MhYVFAcyNzYyFwEnqyghKzcnGxwpF0ELOighKzcnGxwpF0ELOigfKzcnGxwpFz8LOgEg\
HBMrNyY4KRdILgIVA/6pLf48DCgoICAfGR0bIsoMKCggIB8ZHRsiygwoKCAgHxkdGyLLCQQpKB8g\
HhkdG00CAvtuDAAAAAEAAP0MAhkCrgBWAAAlBiMiJjU0NjMyFhUUBzI/AQYjIiY1NDYzMhYVFAcy\
PwEiDgEjIiY1NDYzMhYVFAcyNzYyFwEnEwYjIiY1NDYzMhYVFAcyPwEGIyImNTQ2MzIWFRQHMjcB\
PyghKzcnGxwpF0ELNigfKzcnGxwpFz8LNAEgHBMrNycbHCkXSC4BFgP+ai1VKCErNycbHCkXQQs6\
KCErNycbHCkXQQs0DCgoICAfGR0bIsQMKCggIB8ZHRsixwkEKSgfIB4ZHRtNAgL6gAwBJAwoKCAg\
HxkdGyLIDCgoICAfGR0bIgAD//D/BgImAPoABwAPABMAADYiJjQ2MhYUACImNDYyFhQFATMBUDIj\
IzIjAYgyIyMyI/3SAbh+/kdLIzIjIzL+zyMyIyMyWgH0/gwABP/h/wYDBwD6AAcADwATABcAADYi\
JjQ2MhYUACImNDYyFhQFATMBMwEzAUEyIyMyIwJ3MiMjMiP84wG4e/5HeQG4e/5HSyMyIyMy/s8j\
MiMjMloB9P4MAfT+DAAC/7T/iAF8ARgAEQA7AAA3FjMyNjc2NTQnJiMiBgcGFRQXIicHMzIUKwEi\
NDsBEzY1NCMiDgMHBiY3Njc2MzIWFz4BMzIWFRQGxQIFEjIODQ8CBBI1Cw8nKhktNAsL4QsLS2gG\
CwgMDwsZCgUbBTEPFyUjJAcdJiMeLVkoATUkICYlBQEzHCckKS4geh4eAR0SDA8HFBIsEAgPCVgQ\
GRMaHg80MElrAAAB/9v/9gG+ARgAUwAANwYHBisBIj8BNiYjIgYHBiY3PgMzMhc2MzIXPgEzMhYV\
FA8BBhUUMzI3PgU3NhYHDgIjIiY1ND8BNjU0IyIPAQYHJwYmPwE2NTQjIgdQBwgEBDUNDUIEBggN\
FiQFFQQUECIfEjcLJCQtCQspExkkBS8ECAEEBQsHDAMNAQYVBhIXLR8VGQU0ARUbCEEIDyYNCARD\
ARUbCBURAwEapw0PGjkIDAklGjASKCgoEBgjGgsPfQsJDgIDCQYPBRIBCQ0LHx8aFhMNDYgCBA4U\
qxYBAQEPCKsCBA4UAAAAAf9+/2ABXgG4AEEAAAciJjU0NjMyFhUUBwYVFDMyPgc3IyI1NDsBPgEz\
MhYVFAYjIiY1NDc2NCMiDgcHMzIUKwEOATIgMBcTEhcSChkLEA8LDQoODRQKNRMRQRRpNCAwFxMS\
FxIKGQcMCgcIBQYDBgE2ExM/IXagJiAaIhQPDgsHDQ4GERMlJDw3VScVE0tfJiAaIhQPDgsGHAUL\
ChQNGg0eBijFwQAAAf/bAAABEwETACoAADc+ASYjIgYPAQYHBisBIj8BNiYjIgYHBiY3PgEzMhYX\
NjMyFhUUIyImNTTQBQMDBRQkCT8HCAQENQ0NQgQGCA4XIgUVBCE4IhsdBB8kGiArDxvnAwcELBic\
EQMBGqMNDxo1CAwJPEAaDSceGTcWDRQAAAABAAAAAADcARgAMQAAMyImNTQ2MhYVFAcWMzI2NTQu\
AicmNTQ2MzIWFRQGIyImNTQ3JiMiBhUUHgIXFhUUUB4yFRoXEAwTFiEJCxgGOjctIjYWEA0WBw8R\
DxkREh4FMCsbEBYQDBIQEBYSCw8HDQQlMCMoJBgQGBQOAw4TEQ0JEwwRAyArVQAAAf/k//wA5wEP\
ADwAACcGLgE/ASIGIyImDgEHBicmNz4BNx4BMzI2MzIXFhQPAQYVFDMyNjMyFxY3NiciNTQ2MzIV\
FAYrAS4BIyIKBgsBBbAFHwwDFAwfBQ8EAwgMCwEOLxUiKwQIBQsHngICAQ0GGiMNCAUHJBINJige\
HRglCA4DBAgNBsEGBgIyBQ0LCRIeMAIBBgsBAREHpwYCAwMaCg4NAx0OFTceLwMYAAX/tP+IBUsB\
GAAQACIAtQDGANcAACUWMzI2NzY0JyYjIgYHBhUUBRYzMjY3NjU0JyYjIgYHBhUUFyInBzMyFCsB\
IjQ7ARM2NTQjIg4DBwYmNzY3NjMyFhc+ATMyFz4BMzIWFz4BMzIXPgEzMhYXPgEzMhc+ATMyFhc+\
ATMyFhUUBiMiJwczMhQrASI0OwETNjU0IyIGBxUUBiMiJwczMhQrASI0OwETNjU0IyIGBxUUBiMi\
JwczMhQrASI0OwETNjU0IyIGBxUUBiUWMzI2NzY0JyYjIgYHBhUUBRYzMjY3NjQnJiMiBgcGFRQC\
CgIFEjIODQ8CBBI1Cw/+ywIFEjIODQ8CBBI1Cw8TFhktNAsL4QsLS2gGCwgMDwsZCgUbBTEPFyUj\
JAcdJiMvExIiGyMkBx0mIy8TEiIbIyQHHSYjLxMSIhsjJAcdJiMeLWtJFhktNAsL4QsLS2gGCw4U\
FGtJFhktNAsL4QsLS2gGCw4UFGtJFhktNAsL4QsLS2gGCw4UFGsDgwIFEjIODQ8CBBI1Cw/+ywIF\
EjIODQ8CBBI1Cw8oATUkIUoFATMcJyQpBgE1JCAmJQUBMxwnJCkuIHoeHgEdEgwPBxQSLBAIDwlY\
EBkTGh4PMhwWExoeDzIcFhMaHg8yHBYTGh4PNDBHbSB6Hh4BHRIMDxgjAUdtIHoeHgEdEgwPGCMB\
R20geh4eAR0SDA8YIwFHbSgBNSQhSgUBMxwnJCkGATUkIUoFATMcJyQpAAT/tP+IBAYBGABvAIAA\
kQCjAAAhIicHMzIUKwEiNDsBEzY1NCMiBgcVFAYjIicHMzIUKwEiNDsBEzY1NCMiBgcVFAYjIicH\
MzIUKwEiNDsBEzY1NCMiDgMHBiY3Njc2MzIWFz4BMzIXPgEzMhYXPgEzMhc+ATMyFhc+ATMyFhUU\
BicWMzI2NzY0JyYjIgYHBhUUBRYzMjY3NjQnJiMiBgcGFRQFFjMyNjc2NTQnJiMiBgcGFRQDUhYZ\
LTQLC+ELC0toBgsOFBRrSRYZLTQLC+ELC0toBgsOFBRrSRYZLTQLC+ELC0toBgsIDA8LGQoFGwUx\
DxclIyQHHSYjLxMSIhsjJAcdJiMvExIiGyMkBx0mIx4ta0wCBRIyDg0PAgQSNQsP/ssCBRIyDg0P\
AgQSNQsP/ssCBRIyDg0PAgQSNQsPIHoeHgEdEgwPGCMBR20geh4eAR0SDA8YIwFHbSB6Hh4BHRIM\
DwcUEiwQCA8JWBAZExoeDzIcFhMaHg8yHBYTGh4PNDBHbSgBNSQhSgUBMxwnJCkGATUkIUoFATMc\
JyQpBgE1JCAmJQUBMxwnJCkAAAP/tP+IAsEBGABMAF4AbwAAMyInBzMyFCsBIjQ7ARM2NTQjIg4D\
BwYmNzY3NjMyFhc+ATMyFz4BMzIWFz4BMzIWFRQGIyInBzMyFCsBIjQ7ARM2NTQjIgYHFRQGJxYz\
MjY3NjU0JyYjIgYHBhUUBRYzMjY3NjQnJiMiBgcGFRTIFhktNAsL4QsLS2gGCwgMDwsZCgUbBTEP\
FyUjJAcdJiMvExIiGyMkBx0mIx4ta0kWGS00CwvhCwtLaAYLDhQUa0wCBRIyDg0PAgQSNQsPAVUC\
BRIyDg0PAgQSNQsPIHoeHgEdEgwPBxQSLBAIDwlYEBkTGh4PMhwWExoeDzQwR20geh4eAR0SDA8Y\
IwFHbSgBNSQgJiUFATMcJyQpBgE1JCFKBQEzHCckKQAAAAL/2/+IAysBGAByAIMAACU2NzYzMhYX\
PgEzMhYVFAYjIicHMzIUKwEiNDsBEzY1NCMiDgMHDgEjIiY1ND8BNjU0IyIPAQYHJwYmPwE2NTQj\
Ig8BBgcGKwEiPwE2JiMiBgcGJjc+AzMyFzYzMhc+ATMyFhUUDwEGFRQzMjc+ARcWMzI2NzY0JyYj\
IgYHBhUUAa0xFBgiIyQHHSYjHi1rSRYZLTQLC+ELC0toBgsHDxIOGAgaPC0VGQU0ARUbCEEIDyYN\
CARDARUbCEQHCAQENQ0NQgQGCA0WJAUVBBQQIh8SNwskJC0JCykTGSQFLwQIAQQPJdECBRIyDg0P\
AgQSNQsPd2wYHRMaHg80MEdtIHoeHgEdEgwPDR8bNRA2OBYTDQ2IAgQOFKsWAQEBDwirAgQOFKwR\
AwEapw0PGjkIDAklGjASKCgoEBgjGgsPfQsJDgIINTkBNSQhSgUBMxwnJCkAAAAAAv/b/2ADGQG4\
AFMAlQAANwYHBisBIj8BNiYjIgYHBiY3PgMzMhc2MzIXPgEzMhYVFA8BBhUUMzI3PgU3NhYHDgIj\
IiY1ND8BNjU0IyIPAQYHJwYmPwE2NTQjIgcTIiY1NDYzMhYVFAcGFRQzMj4HNyMiNTQ7AT4BMzIW\
FRQGIyImNTQ3NjQjIg4HBzMyFCsBDgFQBwgEBDUNDUIEBggNFiQFFQQUECIfEjcLJCQtCQspExkk\
BS8ECAEEBQsHDAMNAQYVBhIXLR8VGQU0ARUbCEEIDyYNCARDARUbCPUgMBcTEhcSChkLEA8LDQoO\
DRQKNRMRQRRpNCAwFxMSFxIKGQcMCgcIBQYDBgE2FBQ/IXYVEQMBGqcNDxo5CAwJJRowEigoKBAY\
IxoLD30LCQ4CAwkGDwUSAQkNCx8fGhYTDQ2IAgQOFKsWAQEBDwirAgQOFP6fJiAaIhQPDgsHDQ4G\
ERMlJDw3VScVE0tfJiAaIhQPDgsGHAULChQNGg0eBijFwQAAAAH/fv9gAmkBuAB0AAAlIw4BIyIm\
NTQ2MzIWFRQHBhUUMzI+BzcjIjU0OwE+ATMyFhUUBiMiJjU0NzY0IyIHBgcXPgEzMhYVFAYjIiY1\
NDc2NCMiDgcHMzIUKwEOASMiJjU0NjMyFhUUBwYVFDMyPgcBX5shdl8gMBcTEhcSChkLEA8LDQoO\
DRQKNRMRQRRpNCAwFxMSFxIKGSYXAwGbFGk0IDAXExIXEgoZBwwKBwgFBgMGATYUFD8hdl8gMBcT\
EhcSChkLEA8LDQoPDRTmxcEmIBoiFA8OCwcNDgYREyUkPDdVJxUTS18mIBoiFA8OCwYccwwGAUtf\
JiAaIhQPDgsGHAULChQNGg0eBijFwSYgGiIUDw4LBw0OBhETJSQ8N1UAAAAB/37/YAN0AbgAqgAA\
EzM+ATMyFhUUBiMiJjU0NzY0IyIHBgcXPgEzMhYVFAYjIiY1NDc2NCMiDgcHMzIUKwEOASMiJjU0\
NjMyFhUUBwYVFDMyPgc3Iw4BIyImNTQ2MzIWFRQHBhUUMzI+BzcjDgEjIiY1NDYzMhYVFAcGFRQz\
Mj4HNyMiNTQ7AT4BMzIWFRQGIyImNTQ3NjQjIg4CBwbMnBRpNCAwFxMSFxIKGSYXAwGbFGk0IDAX\
ExIXEgoZBwwKBwgFBgMGATYTEz8hdl8gMBcTEhcSChkLEA8LDQoPDRQKmyF2XyAwFxMSFxIKGQsQ\
DwsNCg4NFQqbIXZfIDAXExIXEgoZCxAPCw0KDg0UCjUTEUEUaTQgMBcTEhcSChkOFQ4IBgIBDktf\
JiAaIhQPDgsGHHMMBgFLXyYgGiIUDw4LBhwFCwoUDRoNHgYoxcEmIBoiFA8OCwcNDgYREyUkPDdV\
J8XBJiAaIhQPDgsHDQ4GERMlJDs4VSfFwSYgGiIUDw4LBw0OBhETJSQ8N1UnFRNLXyYgGiIUDw4L\
BhwTJyIbCgAB/37/YASAAbgA4AAAARc+ATMyFhUUBiMiJjU0NzY0IyIHBgcXPgEzMhYVFAYjIiY1\
NDc2NCMiDgcHMzIUKwEOASMiJjU0NjMyFhUUBwYVFDMyPgc3Iw4BIyImNTQ2MzIWFRQHBhUUMzI+\
BzcjDgEjIiY1NDYzMhYVFAcGFRQzMj4HNyMOASMiJjU0NjMyFhUUBwYVFDMyPgc3IyI1NDsBPgEz\
MhYVFAYjIiY1NDc2NCMiBwYHFz4BMzIWFRQGIyImNTQ3NjQjIg4HAdicFGk0IDAXExIXEgoZJhcD\
AZsUaTQgMBcTEhcSChkHDAoHCAUGAwYBNhMTPyF2XyAwFxMSFxIKGQsQDwsNCg8NFAqbIXZfIDAX\
ExIXEgoZCxAPCw0KDg0UCpshdl8gMBcTEhcSChkLEA8LDQoPDRQKmyF2XyAwFxMSFxIKGQsQDwsN\
Cg4NFAo1ExFBFGk0IDAXExIXEgoZJhcDAZsUaTQgMBcTEhcSChkHDAoHCAUGAwYBDwFLXyYgGiIU\
Dw4LBhxzDAYBS18mIBoiFA8OCwYcBQsKFA0aDR4GKMXBJiAaIhQPDgsHDQ4GERMlJDw3VSfFwSYg\
GiIUDw4LBw0OBhETJSM8N1YnxcEmIBoiFA8OCwcNDgYREyUkPDdVJ8XBJiAaIhQPDgsHDQ4GERMl\
JDw3VScVE0tfJiAaIhQPDgsGHHMMBgFLXyYgGiIUDw4LBhwFCgsTDhkOHQAAAAADAAD/YALfAbgA\
NgBxALYAADMiJjU0NjMyFhUUBxYzMjY1NC4DJy4CNTQ2MzIWFRQGIyImNTQ3JiMiBhUUHgIXHgEV\
FCUGLgE/ASYjIgYjIg4BBwYnJjc+ATcWMzI2MzIXFhQPAQYVFB4BFxY+AScuATU0NjMyFRQGKwEu\
ASMiBSImNTQ2MzIWFRQHDgEeARUUFjI+BzcjIjU0OwE+ATMyFhUUBiMiJjU0NzY0IyIOBwczMhQr\
AQ4BUB4yEgwOFwwGGBYhAwwFFwMUGBQ3LSI2FhANFgUQDg8ZERIeBRsXASYGCwEFrgoUBRoJBRAS\
BRAFAwcLDAExGiAoBwIUCweaBBoqDQUJBQIDJBQMJisgBhc1CRP+tCAwFxMSFxIEAQECBhYQDwsN\
Cg4NFAo1ExFBFGk0IDAXExIXEgoZBwwKBwgFBgMGATYTEz8hdisbEBYNCxgMEhYSCQwNBA4CDRIe\
DyMtJBgQGBQOBgoUEQ0JEwwRAxIfFVoDBAgNBr8FARgfBA0LCBMeLgQDCAIBEQejBQUHAwcLAwQN\
BggIFAsTNx4qAg2yJiAaIhQPDgsCBQQGAwgGBhETJSQ8N1UnFRNLXyYgGiIUDw4LBhwFCwoUDRoN\
HgYoxcEAAgAF//sB/AGaAAkALAAAAQ8BBhUUMzI2NwcOASMiJjU0PwEjNTM/AQc3MhU2MzIWFRQG\
IiY1NDcGDwEjATZ/OQIUGEQSDCkyHyIiAzdpcxZZJpgZIDUYHRggFgk3EEJIAQkHuAgDFRcPKBkU\
JBoLC7MgSi54Ci0pHBUSGxEOEhMPJ9gAAQAA//0BtQDUAC8AACUyNTQnBiImNT4BMzIWFRQHBiMi\
LwEmIyIVFBc2MzIWFRQGByInJjU0NzYzFh8BFgFwLRoQHBQBFwkkLSkXHiYeohoRLhkQDw0VFA0d\
GBwoFiIqF6IcMTkgExAWDQ4WMzUzJBUVehI6IBIQFw4PEgIaIi0zJBMCEHoTAAEAAP/NAbUBAwA2\
AAAXIiY1NDc2MxYfATUzFRcWMzI2NTQnBiMiNT4BMzIWFRQHBiMiLwEVIzUnJiMiBhUUFzYzMhUU\
TSAtKBYgKBcuHlwiDxUcFhQSHAEPCSAtKRccJB4uHlwgDxYcFRQTHANDJjMkEwIQJGmARxcpHCcQ\
DBsPFUImMyQVFSRsg0cWKB4nDwwdIAABAA0AAAJFAOAACwAANyc3FzcXNxcHJwcnIhWOZXhqTRaS\
aXRpLhmZfHx8VBehfHx8AAAAAQAN/8sCRQERABMAACUHJwcnNxc3NTMXNxc3FwcnBxUjARZAaUsV\
jmUWGwFGak8UkmkZG0ZGfE4ZmXwXlntKfFUYoXwblgAAAQAAAAABGAEYAAsAADM1IzUzNTMVMxUj\
FXt7eyJ7e3sie3siewAAAAEAAP6OATYAAAAKAAARNT4ENzMUBiQxTDQ0Dx7A/o48Bw8rPW9JjtoA\
AAAAAf//AAABLQCgAB0AADc+AjMyHgEXFjMyNzYWBw4CIyIuAScmIyIHBiYBCRApHBgmJg8JCh4Y\
BA4CCBEpHBgkJRIHCB0dBQ1GGSEgITEKBiQGBwcZIh8hMQoEIwYIAAAAAQAAAAABLAEsAAcAADER\
IREjNSMVASwj5gEs/tS0tAABAAAAAAD6AcIABgAAMwMzGwEzA2lpKFVVKGkBwv6YAWj+PgACAAAA\
AADIAMgABwAPAAA2MjY0JiIGFBYiJjQ2MhYURT4sLD4sdFI7O1I7GSw+LCw+RTtSOztSAAH/OAAA\
AMgAyAALAAAjNDYyFhUjNCYiBhXIdqR2HmCUYFJ2dlJKYGBKAAAAAgAAAAAAtAEsAAcAFQAANjI2\
NCYiBhQXNS4BNTQ2MhYVFAYHFUseGxseGxgdKzdGNysdeDU2NTU2rWQINScoPDwoJzUIZAAAAgAA\
AAAAyAEsAA8AHwAANy4BNTQ2MhYVFAYHHQEjNTc+ATU0JiIGFRQWFz0BMxVUJDA7UjswJCAgGSIs\
PiwiGSBmBTglKTs7KSU4BQFlZRkGKRsfLCwfGykGAUlJAAAABAAA//wD9AJ/AIMAjQCZAKMAADc+\
ATU0Jy4BNTQ+Aj8CDgEVFDMyNxcOASMiJjU0PgIzMhYVFAYjIiYnNx4BMzI1NC4CJwcGFRQeAhUU\
Bg8BHgI7ATI3JjU0NzYzMhYVFAcGBx4BMzI2NTQ2Ny4CPQEeARUUBiMiJwYjIi4BJw4BIiYnJiMi\
DgEHBiMiNTQ2BSImNDYzMhYUBiU+ATU0JyYjIgYVFAU2NTQmJw4BFRSYNkUFA1IDBAUBAj1iaiAe\
HRoVKSUeLR8+cEh9cjIuHDYSGBEUFDMMHEAtGwkdIh0fEBAOKR8MDREiIRwiTBsjCh08EyAbFjFD\
XSCBXrq0UUJBIyQ8ITMWDiwoIBscGhIKFBkGWiEKMANcCxMTCwwSEf4PMyQEBRUcLgE8WhcSPzBa\
GD8gCgwIdCIFDg4MBASlBGMsIzwNQi8mGiFHRCxRNzA1MCsOHhEyChoiGgJCJBseNiEsFSZPFBQK\
KRoeNSg1JS4rICETOTAXEioZUl8ZLFk0BgE8sWtGXzk6FRQSJhkWIC0LFQQ6Bw04TxIYEhIYEoAv\
LxsHDhI5Kx50BYEiSRUdWEQ/AAACAAwACgHTAc8ACgCPAAAlNCYjIgYUFjMyNicOASMiJjQ2MzIW\
FzY1NCcmIyImNDYzMhceARcWMzI1NCcuATU0NjMyFhUUBgcUMzI3PgE3NjMyFhUUBiMiBgcGFRQz\
MjYzMhYUBiMiJiMiBhUUFx4BFxYVFAYjIicuAScmIyIVFBYVFAYjIiY0NjU0IyIHDgEHBiMiJjU0\
NzYzMjc2NTQBFhgPEBUWDxAXiRkmDhkbGhkNKhglDBQYHRwZFxIQCwMUCxIUAQInHhQSGyMBFhAN\
EQENDBoTHhsSGxUNECEbKw4cGx0ZDycUFxILFDgNDhkXExQMARMRChIqIBMSHSYXDQ4SAxESDBQa\
DQwWIxIN7hAUEyIWFQoBJRkqGyUCAxYOChMcKB0MCz4WDRgNCBcnDxcaGxYRIxkqEBM4Dw4bFBEj\
BgsODxYnHSgaJggKFgoSAwsNGRMbDgs5FQ8fHjATFBgaJDEXJA4SPQwHFhQYDg0SDQwaAAAAAwAA\
/wYB9AD6AAcADwAXAAAEIiY0NjIWFCQUFjI2NCYiAjQ2MhYUBiIBEzIjIzIj/vd4qnh4qqWS0JKS\
0DwjMiMjMm6qeHiqeP7L0JKS0JIAAAAAAgAA/wYB9AD6AAcADwAANhQWMjY0JiICNDYyFhQGIi14\
qnh4qqWS0JKS0FWqeHiqeP7L0JKS0JIAAAAAAwAA/qIB9AFeAAUAFgAcAAAXEQ4BFBYTNTMVHgEU\
BgcVIzUuATU0NhM+ATQmJ+NNaWlNLWGDg2EtYIODjU5paU7MAZgIdpx2Ab1lZQiPxI8IZWUJj2Fi\
j/5DCHWedQgAAgAA/wYB0gD6AAcAKQAABCImNDYyFhQ3FhUUBwYjIicmIyIGFBYzMjc2MzIXFhUU\
BwYjIiY0NjMyARMyIyMyI5kCCwUGDQozd1V4eFV4MgYSBwMMA0CVaJKSaJQ8IzIjIzKbBgQPBgMO\
X3iqeF8NAgcMBgZ4ktCSAAAAAAEAAP8GAdIA+gAhAAAlFhUUBwYjIicmIyIGFBYzMjc2MzIXFhUU\
BwYjIiY0NjMyAc8CCwUGDQozd1V4eFV4MgYSBwMMA0CVaJKSaJSCBgQPBgMOX3iqeF8NAgcMBgZ4\
ktCSAAACAAD+ogHSAV4AJAAqAAAlFhUUBwYjIicmJxE2NzYzMhcWFRQHBgcVIzUuATU0Njc1MxUW\
Bw4BFBYXAc8CCwUGDQouZmYuBhIHAwwDO4QtYIODYC2EsU1paU2CBgQPBgMOVAr+aAdXDQIHDAYG\
cAdlZQmPYWKPCGVlCSQIdpx2CAAAAAACAAD9EgFKAL4AAwAPAAA3FSE1JTMVITUzESMRIRUjHgEO\
/tQeAQ4eHv7yHkaMjHgyMvxUAmIyAAEAAAAAAlYBcgALAAA1Nxc3FzcXAScHJweJVFZSryL+/FRW\
UzVBuXNzcekW/qR0dHBHAAAB/+gAvQESATcAFwAAJj4BMzIWMjc2MzIVFA4BIyImIgcGIyI1GDAh\
HBRVKBQDBw4wIRwUVSgUBAcN9jARPRQDBwwwET0UBAgAAAAAAgAA/4kBwgCLAAsAEwAABTQmIyIG\
FRQWMzI+ARQGIiY0NjIBUVo2IDRdNyEvcX3IfX3IFDRPJx80TCN0bEtLbEsAAAACAAD/bAFIAqgA\
DQAcAAAlJiMiBhUUFxYzMjY1NBMzERQGIyInJjU0NjMyFwEkDSU8lwYLJjyXAh1+SE8kD35IQCRE\
F2ErCgkXYSsJAm79bkNnQx4dQ2ctAAAAAAEAAP95AUACqAANAAABMxEUBiMiJjU0NjMyFwEiHnlZ\
Mjx6WDEfAqj9gEZpOCdFahwAAAABAAD/eQIKAqgAGgAAAREUBiMiJjU0NjMyFxE1Mx4EFRQHNjU0\
AUB5WTI8elgxHx4GLjk4JTISAbn+b0ZpOCdFahwBTu81XEdOdUlIckFJ7wAAAAACAAD/eQILAqgA\
HwAoAAAlNREzHgQVFAcWFRQHNjU0JiMRFAYjIiY1NDYzMhMeARc0NjU0JgEiHgkwODYjFRYiBXY4\
eVkyPHpYMT0OhRsBdmvmAVckRztCWDMlMjAkLkEZIV+e/tZGaTgnRWoBdjuiNQMOA1+fAAABAAD/\
zgBkADIABwAAFiImNDYyFhRHKh0dKh0yHSodHSoAAAAAAA4ArgABAAAAAAAAAIMBCAABAAAAAAAB\
AAcBnAABAAAAAAACAAcBtAABAAAAAAADACMCBAABAAAAAAAEAAcCOAABAAAAAAAFAAgCUgABAAAA\
AAAGAAcCawADAAEECQAAAQYAAAADAAEECQABAA4BjAADAAEECQACAA4BpAADAAEECQADAEYBvAAD\
AAEECQAEAA4CKAADAAEECQAFABACQAADAAEECQAGAA4CWwBDAG8AcAB5AHIAaQBnAGgAdAAgAFwA\
MgA1ADEAIAAyADAAMQA4AC0AMgAwADIANQAgAEoAZQBhAG4ALQBGAHIAYQBuAGMAbwBpAHMAIABN\
AG8AaQBuAGUALgAgAFQAaABpAHMAIABmAG8AbgB0ACAAaQBzACAAbABpAGMAZQBuAHMAZQBkACAA\
dQBuAGQAZQByACAAdABoAGUAIABTAEkATAAgAE8AcABlAG4AIABGAG8AbgB0ACAATABpAGMAZQBu\
AHMAZQAgAFwAKABoAHQAdABwADoALwAvAHMAYwByAGkAcAB0AHMALgBzAGkAbAAuAG8AcgBnAC8A\
TwBGAEwAXAApAC4AAENvcHlyaWdodCBcMjUxIDIwMTgtMjAyNSBKZWFuLUZyYW5jb2lzIE1vaW5l\
LiBUaGlzIGZvbnQgaXMgbGljZW5zZWQgdW5kZXIgdGhlIFNJTCBPcGVuIEZvbnQgTGljZW5zZSBc\
KGh0dHA6Ly9zY3JpcHRzLnNpbC5vcmcvT0ZMXCkuAABhAGIAYwAyAHMAdgBnAABhYmMyc3ZnAABS\
AGUAZwB1AGwAYQByAABSZWd1bGFyAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAGEA\
YgBjADIAcwB2AGcAIAA6ACAAMQAyAC0AMgAtADIAMAAyADUAAEZvbnRGb3JnZSAyLjAgOiBhYmMy\
c3ZnIDogMTItMi0yMDI1AABhAGIAYwAyAHMAdgBnAABhYmMyc3ZnAABWAGUAcgBzAGkAbwBuACAA\
AFZlcnNpb24gAABhAGIAYwAyAHMAdgBnAABhYmMyc3ZnAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAA\
AAAAAAAAAAAAAAAAAJEAAAABAAIBAgADAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIB\
EwEUARUBFgEXARgBGQEaARsBHAEdAR4BHwEgASEBIgEjASQBJQEmAScBKAEpASoBKwEsAS0BLgEv\
ATABMQEyATMBNAE1ATYBNwE4ATkBOgE7ATwBPQE+AT8BQAFBAUIBQwFEAUUBRgFHAUgBSQFKAUsB\
TAFNAU4BTwFQAVEBUgFTAVQBVQFWAVcBWAFZAVoBWwFcAV0BXgFfAWABYQFiAWMBZAFlAWYBZwFo\
AWkBagFrAWwBbQFuAW8BcAFxAXIBcwF0AXUBdgF3AXgBeQF6AXsBfAF9AX4BfwGAAYEBggGDAYQB\
hQGGAYcBiAGJAYoBiwGMAY0BjgYubm9kZWYHdW5pRTAwMAd1bmlFMDMwB3VuaUUwMzgHdW5pRTAz\
OQd1bmlFMDQzB3VuaUUwNDQHdW5pRTA0NQd1bmlFMDQ2B3VuaUUwNDcHdW5pRTA0OAd1bmlFMDUw\
B3VuaUUwNUMHdW5pRTA2Mgd1bmlFMDY5B3VuaUUwN0EHdW5pRTA3Qgd1bmlFMDdDB3VuaUUwN0QH\
dW5pRTA3RQd1bmlFMDgwB3VuaUUwODEHdW5pRTA4Mgd1bmlFMDgzB3VuaUUwODQHdW5pRTA4NQd1\
bmlFMDg2B3VuaUUwODcHdW5pRTA4OAd1bmlFMDg5B3VuaUUwOEEHdW5pRTA4Qgd1bmlFMDhDB3Vu\
aUUwOTQHdW5pRTA5NQd1bmlFMEEwB3VuaUUwQTEHdW5pRTBBMgd1bmlFMEEzB3VuaUUwQTQHdW5p\
RTBBOQd1bmlFMEIzB3VuaUUxMDEHdW5pRTFCOQd1bmlFMUJCB3VuaUUxRTcHdW5pRTFGMgd1bmlF\
MUY0B3VuaUUxRjcHdW5pRTFGOQd1bmlFMUZDB3VuaUUxRkUHdW5pRTFGRgd1bmlFMjAwB3VuaUUy\
NDAHdW5pRTI0MQd1bmlFMjQyB3VuaUUyNDMHdW5pRTI0NAd1bmlFMjQ1B3VuaUUyNDYHdW5pRTI0\
Nwd1bmlFMjQ4B3VuaUUyNDkHdW5pRTI2MAd1bmlFMjYxB3VuaUUyNjIHdW5pRTI2Mwd1bmlFMjY0\
B3VuaUUyNmEHdW5pRTI2Ygd1bmlFMjgwB3VuaUUyODEHdW5pRTI4Mgd1bmlFMjgzB3VuaUU0QTAH\
dW5pRTRBMgd1bmlFNEE0B3VuaUU0QTgHdW5pRTRBQwd1bmlFNEMwB3VuaUU0Q0UHdW5pRTREMQd1\
bmlFNEUxB3VuaUU0RTIHdW5pRTRFMwd1bmlFNEU0B3VuaUU0RTUHdW5pRTRFNgd1bmlFNEU3B3Vu\
aUU0RTgHdW5pRTRFOQd1bmlFNEVBB3VuaUU1MDAHdW5pRTUwMQd1bmlFNTIwB3VuaUU1MjEHdW5p\
RTUyMgd1bmlFNTIzB3VuaUU1MjQHdW5pRTUyNQd1bmlFNTI5B3VuaUU1MkEHdW5pRTUyQgd1bmlF\
NTJDB3VuaUU1MkQHdW5pRTUyRgd1bmlFNTMwB3VuaUU1MzEHdW5pRTUzOQd1bmlFNTY2B3VuaUU1\
NjcHdW5pRTU2OQd1bmlFNTZDB3VuaUU1NkQHdW5pRTU4Mgd1bmlFNUQwB3VuaUU1RTIHdW5pRTYx\
MAd1bmlFNjEyB3VuaUU2MTQHdW5pRTYxOAd1bmlFNjI0B3VuaUU2MzAHdW5pRTY1MAd1bmlFNjU1\
B3VuaUU5MTAHdW5pRTkxMQd1bmlFOTEyB3VuaUU5MTQHdW5pRTkxNQd1bmlFOTE4B3VuaUU5NUQH\
dW5pRUEwMgd1bmlFQUE0B3VuaUVDQTIHdW5pRUNBMwd1bmlFQ0E1B3VuaUVDQTcHdW5pRUNBOQd1\
bmlFQ0I3AAAAAAH//wACAAEAAAAAAAAADAAUAAQAAAACAAAAAQAAAAEAAAAAAAEAAAAA44To7gAA\
AADRlyIXAAAAAOPSSjM=\
") format("truetype")'
// abc2svg - format.js - formatting functions
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

    var	font_scale_tb = {
		serif: 1,
		serifBold: 1,
		'sans-serif': 1,
		'sans-serifBold': 1,
		Palatino: 1.1,
		monospace: 1
	},
	txt_ff = "text,serif",		// text font-family (serif for compatibility)
	ff = {},			// font-face's from %%beginsvg
	fmt_lock = {}

var cfmt = {
	"abc-version": "1",		// default: old version
	annotationfont: {name: "text,sans-serif", size: 12},
	aligncomposer: 1,
	beamslope: .4,			// max slope of a beam
//	botmargin: .7 * IN,		// != 1.8 * CM,
	bardef: {
		"[":	"",		// invisible
		"[]":	"",
		"|:":	"[|:",
		"|::":	"[|::",
		"|:::":	"[|:::",
		":|":	":|]",
		"::|":	"::|]",
		":::|":	":::|]",
		"::":	":][:"
	},
	breaklimit: .7,
	breakoneoln: true,
	cancelkey: true,
	composerfont: { name: txt_ff, style: "italic", size: 14 },
	composerspace: 6,
//	contbarnb: false,
	decoerr: true,
	dynalign: true,
	footerfont: { name: txt_ff, size: 16 },
	fullsvg: '',
	gchordfont: { name: "text,sans-serif", size: 12 },
	gracespace: new Float32Array([6, 8, 11]),	// left, inside, right
	graceslurs: true,
	headerfont: { name: txt_ff, size: 16 },
	historyfont: { name: txt_ff, size: 16 },
	hyphencont: true,
	indent: 0,
	infofont: {name: txt_ff, style: "italic", size: 14 },
	infoname: 'R "Rhythm: "\n\
B "Book: "\n\
S "Source: "\n\
D "Discography: "\n\
N "Notes: "\n\
Z "Transcription: "\n\
H "History: "',
	infospace: 0,
	keywarn: true,
	leftmargin: 1.4 * CM,
	lineskipfac: 1.1,
	linewarn: true,
	maxshrink: .65,		// nice scores
	maxstaffsep: 2000,
	maxsysstaffsep: 2000,
	measrepnb: 1,
	measurefont: {name: txt_ff, style: "italic", size: 10},
	measurenb: -1,
	musicfont: {name: "music", src: musicfont, size: 24},
	musicspace: 6,
//	notespacingfactor: "1.3, 38",
	partsfont: {name: txt_ff, size: 15},
	parskipfac: .4,
	partsspace: 8,
//	pageheight: 29.7 * CM,
	pagewidth: 21 * CM,
	"propagate-accidentals": "o",		// octave
	printmargin: 0,
	rightmargin: 1.4 * CM,
	rbmax: 4,
	rbmin: 2,
	repeatfont: {name: txt_ff, size: 9},
	scale: 1,
	slurheight: 1.0,
	spatab: 	// spacing table (see "notespacingfactor" and set_space())
		new Float32Array([	// default = "1.3, 38"
			10.2, 13.3, 17.3, 22.48, 29.2,
			38,
			49.4, 64.2, 83.5, 108.5]),
	staffsep: 46,
	stemheight: 21,			// one octave
	stretchlast: .25,
	stretchstaff: true,
	subtitlefont: {name: txt_ff, size: 16},
	subtitlespace: 3,
	sysstaffsep: 34,
	systnames: -1,			// (for compatibility)
	systvoices: 3,
	tempofont: {name: txt_ff, weight: "bold", size: 12},
	textfont: {name: txt_ff, size: 16},
//	textoption: undefined,
	textspace: 14,
	tieheight: 1.0,
	titlefont: {name: txt_ff, size: 20},
//	titleleft: false,
	titlespace: 6,
	titletrim: true,
//	transp: 0,			// global transpose
//	topmargin: .7 * IN,
	topspace: 22,
	tuplets: [0, 0, 0, 0],
	tupletfont: {name: txt_ff, style: "italic", size: 10},
	vocalfont: {name: txt_ff, weight: "bold", size: 13},
	vocalspace: 10,
	voicefont: {name: txt_ff, weight: "bold", size: 13},
//	voicescale: 1,
	writefields: "CMOPQsTWw",
	wordsfont: {name: txt_ff, size: 16},
	wordsspace: 5,
	"writeout-accidentals": "n"
}

// parameters that are used in the symbols
var sfmt = {
bardef: true,
barsperstaff: true,
beamslope: true,
breaklimit: true,
bstemdown: true,
cancelkey: true,
dynalign: true,
flatbeams: true,
gracespace: true,
hyphencont: true,
keywarn: true,
maxshrink: true,
maxstaffsep: true,
measrepnb: true,
rbmax: true,
rbmin: true,
shiftunison: true,
slurheight: true,
squarebreve: true,    
staffsep: true,
systvoices: 1, //true
stemheight: true,
stretchlast: true,
stretchstaff: true,
tieheight: true,
timewarn: true,
trimsvg: 1, //true
vocalspace: true
} // sfmt

function get_bool(param) {
	return !param || !/^(0|n|f)/i.test(param) // accept void as true !
}

// %%font <font> [<encoding>] [<scale>]
function get_font_scale(param) {
    var	i, font,
	a = info_split(param)	// a[0] = font name

	if (a.length <= 1)
		return
	var scale = +a[a.length - 1]

	if (isNaN(scale) || scale <= 0.5) {
		syntax(1, "Bad scale value in %%font")
		return
	}
	font_scale_tb[a[0]] = scale
}

// set the width factor of a font
function set_font_fac(font) {
    var scale = font_scale_tb[font.fname || font.name]

	if (!scale)
		scale = 1.1;
	font.swfac = font.size * scale
}

// %%xxxfont fontname|* [encoding] [size|*]
function param_set_font(xxxfont, p) {
    var	font, n, a, ft2, k

	// "setfont-<n>" goes to "u<n>font"
	if (xxxfont[xxxfont.length - 2] == '-') {
		n = xxxfont[xxxfont.length - 1]
		if (n < '1' || n > '9')
			return
		xxxfont = "u" + n + "font"
	}

	// fill the values
	font = {}
	a = p.match(/\s+(no)?box(\s|$)/)
	if (a) {				// if box
		if (a[1]) {
			font.box = false	// nobox
			font.pad = 0
		} else {
			font.box = true
			font.pad = 2.5
		}
		p = p.replace(a[0], a[2])
	}
	a = p.match(/\s+padding=([\d.]+)(\s|$)/)
	if (a) {				// if padding
		font.pad = a[1] ? +a[1] : 0
		p = p.replace(a[0], a[2])
	}

	a = p.match(/\s+class=(.*?)(\s|$)/)
	if (a) {
		font.class = a[1];
		p = p.replace(a[0], a[2])
	}
	a = p.match(/\s+wadj=(.*?)(\s|$)/)
	if (a) {
	    if (typeof document == "undefined")	// useless if in browser
		switch (a[1]) {
		case 'none':
			font.wadj = ''
			break
		case 'space':
			font.wadj = 'spacing'
			break
		case 'glyph':
			font.wadj = 'spacingAndGlyphs'
			break
		default:
			syntax(1, errs.bad_val, "%%" + xxxfont)
			break
		}
		p = p.replace(a[0], a[2])
	}

	// the font size is the last item
	a = p.match(/\s+([0-9.]+|\*)$/)
	if (a) {
		if (a[1] != "*")
			font.size = +a[1]
		p = p.replace(a[0], "")
	}

	// accept local(..) and url(...) as the font source
	if ((p[0] == 'u' && p.slice(0, 4) == "url(")
	 || (p[0] == 'l' && p.slice(0, 6) == "local(")) {
		n = p.indexOf(')', 1)
		if (n < 0) {
			syntax(1, "No end of url in font family")
			return
		}

		font.src = p.slice(0, n + 1)
		font.fid = abc2svg.font_tb.length
		abc2svg.font_tb.push(font)
		font.name = 'ft' + font.fid
		p = p.replace(font.src, '')
	}

		// extract the font attributes
		a = p.match(/[- ]?[nN]ormal/)
		if (a) {
			font.normal = true
			p = p.replace(a[0], '')
		}

		// font weight
		a = p.match(abc2svg.ft_re)
		if (a) {
			font.weight = abc2svg.ft_w[a[0].replace(/[ -]/, '')
					.toLowerCase()]
			p = p.replace(a[0], '')
		}

		a = p.match(/[- ]?[iI]talic/)
		if (a) {
			font.style = "italic"
			p = p.replace(a[0], '')
		}
		a = p.match(/[- ]?[oO]blique/)
		if (a) {
			font.style = "oblique"
			p = p.replace(a[0], '')
		}

	if (!font.src) {			// if no url(...)

		// here is the font family
		p = p.trim()

		if (p == '*')
			p = ''
		p = p.replace(/Times-Roman|Times/, "serif")
			.replace("Helvetica", "sans-serif")
			.replace("Courier", "monospace")
			.replace("music", cfmt.musicfont.name)
//hack: the font "Figurato" is used for figured bass
			if (p.indexOf("Fig") > 0)
				font.figb = true
	}
	if (p && !font.name)
		font.name = p

	if (font.size)
		set_font_fac(font)

	// keep the previous attributes if no font name or no size
	if (!font.name || !font.size) {
		ft2 = cfmt[xxxfont]
		for (k in ft2) {
			if (!ft2.hasOwnProperty(k)
			 || font[k] != undefined)
				continue
			switch (k) {
			case "fid":
			case "used":
			case "src":
				break
			case "style":
			case "weight":
				if (font.normal)
					break
				// fall thru
			default:
				font[k] = ft2[k]
				break
			}
		}
		if (!font.swfac)
			set_font_fac(font)
	}
	if (font.pad == undefined)
		font.pad = 0
	font.fname = font.name
	if (font.weight >= 700)
		font.fname += 'Bold'

	cfmt[xxxfont] = font
}

// get a length with a unit - return the number of pixels
function get_unit(param) {
    var	v = param.toLowerCase().match(/(-?[\d.]+)(.*)/)
	if (!v)
		return NaN

	v[1] = +v[1]
	switch (v[2]) {
	case "cm":
		return v[1] * CM
	case "in":
		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 name of an info or a part
function set_infoname(cmd, param) {
//fixme: check syntax: '<letter> ["string"]'
    var	tmp = cfmt[cmd] ? cfmt[cmd].split("\n") : "",
		letter = param[0]

	for (var i = 0; i < tmp.length; i++) {
		var infoname = tmp[i]
		if (infoname[0] != letter)
			continue
		if (param.length == 1)
			tmp.splice(i, 1)
		else
			tmp[i] = param
		cfmt[cmd] = tmp.join('\n')
		return
	}
	if (cfmt[cmd])
		cfmt[cmd] += "\n" + param
	else
		cfmt[cmd] = param
}

// get the text option
var textopt = {
	align: 'j',
	center: 'c',
	fill: 'f',
	justify: 'j',
	obeylines: 'l',
	ragged: 'f',
	right: 'r',
	skip: 's',
// abcm2ps compatibility
	"0": 'l',
	"1": 'j',
	"2": 'f',
	"3": 'c',
	"4": 's',
	"5": 'r'
}
function get_textopt(v) {
    var	i = v.indexOf(' ')
	if (i > 0)
		v = v.slice(0, i)
	return textopt[v]
}

/* -- position of a voice element -- */
var posval = {
	above: C.SL_ABOVE,
	auto: 0,		// !! not C.SL_AUTO !!
	below: C.SL_BELOW,
	down: C.SL_BELOW,
	hidden: C.SL_HIDDEN,
	opposite: C.SL_HIDDEN,
	under: C.SL_BELOW,
	up: C.SL_ABOVE
}

/* -- set the position of elements in a voice -- */
function set_pos(k, v) {		// keyword, value
	k = k.slice(0, 3)
	if (k == "ste")
		k = "stm"
	set_v_param("pos", '"' + k + ' ' + v + '"')
}

// set/unset the fields to write
function set_writefields(parm) {
	var	c, i,
		a = parm.split(/\s+/)

	if (get_bool(a[1])) {
		for (i = 0; i < a[0].length; i++) {	// set
			c = a[0][i]
			if (cfmt.writefields.indexOf(c) < 0)
				cfmt.writefields += c
		}
	} else {
		for (i = 0; i < a[0].length; i++) {	// unset
			c = a[0][i]
			if (cfmt.writefields.indexOf(c) >= 0)
				cfmt.writefields = cfmt.writefields.replace(c, '')
		}
	}
}

// set a voice specific parameter
function set_v_param(k, v) {
	k = [k + '=', v]
	if (parse.state < 3)
		memo_kv_parm(curvoice ? curvoice.id : '*', k)
	else if (curvoice)
		set_kv_parm(k)
	else
		memo_kv_parm('*', k)
}

function set_page() {
	if (!img.chg)
		return
	img.chg = false;
	img.lm = cfmt.leftmargin - cfmt.printmargin
	if (img.lm < 0)
		img.lm = 0;
	img.rm = cfmt.rightmargin - cfmt.printmargin
	if (img.rm < 0)
		img.rm = 0;
	img.width = cfmt.pagewidth - 2 * cfmt.printmargin

	// must have 100pt at least as the staff width
	if (img.width - img.lm - img.rm < 100) {
		error(0, undefined, "Bad staff width");
		img.width = img.lm + img.rm + 150
	}
	set_posx()
} // set_page()

// set a format parameter
// (possible hook)
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)) {
		if (cmd == "soundfont")
			cfmt.soundfont = param
		else
			param_set_font(cmd, param)
		return
	}

	// duplicate the global parameters if already used by symbols
	if (sfmt[cmd] && parse.ufmt)
		cfmt = Object.create(cfmt)

	switch (cmd) {
	case "aligncomposer":
	case "barsperstaff":
	case "infoline":
	case "measurenb":
	case "rbmax":
	case "rbmin":
	case "measrepnb":
	case "shiftunison":
	case "systnames":
	case "systvoices":
		v = parseInt(param)
		if (isNaN(v)) {
			syntax(1, "Bad integer value");
			break
		}
		if (cmd == "systnames") {	// compatibility
			switch (v) {
			case -1: v = 3; break
			case 1: v = 2; break
			case 2: v = 1; break
			}
			cmd = "systvoices"
		}
		cfmt[cmd] = v
		break
	case "abc-version":
	case "bgcolor":
	case "fgcolor":
	case "propagate-accidentals":
	case "writeout-accidentals":
		cfmt[cmd] = param
		break
	case "beamslope":
	case "breaklimit":			// float values
	case "lineskipfac":
	case "maxshrink":
	case "pagescale":
	case "parskipfac":
	case "scale":
	case "slurheight":
	case "stemheight":
	case "tieheight":
		f = +param
		if (isNaN(f) || !param || f < 0) {
			syntax(1, errs.bad_val, '%%' + cmd)
			break
		}
		switch (cmd) {
		case "scale":			// old scale
			f /= .75
		case "pagescale":
			if (f < .1)
				f = .1		// smallest scale
			cmd = "scale";
			img.chg = true
			break
		}
		cfmt[cmd] = f
		break
	case "annotationbox":
	case "gchordbox":
	case "measurebox":
	case "partsbox":
		param_set_font(cmd.replace("box", "font"),	// font
			"* * " + (get_bool(param) ? "box" : "nobox"))
		break
	case "altchord":
	case "bstemdown":
	case "breakoneoln":
	case "cancelkey":
	case "checkbars":
	case "contbarnb":
	case "custos":
	case "decoerr":
	case "flatbeams":
	case "graceslurs":
	case "graceword":
	case "hyphencont":
	case "keywarn":
	case "linewarn":
	case "squarebreve":
	case "splittune":
	case "straightflags":
	case "stretchstaff":
	case "timewarn":
	case "titlecaps":
	case "titleleft":
	case "trimsvg":
		cfmt[cmd] = get_bool(param)
		break
	case "dblrepbar":
		param = ":: " + param
		// fall thru
	case "bardef":			// %%bardef oldbar newbar
		v = param.split(/\s+/)
		if (v.length != 2) {
			syntax(1, errs.bad_val, "%%bardef")
		} else {
			if (parse.ufmt)
				cfmt.bardef = Object.create(cfmt.bardef)	// new object
			cfmt.bardef[v[0]] = v[1]
		}
		break
	case "chordalias":
		v = param.split(/\s+/)
		if (!v.length)
			syntax(1, errs.bad_val, "%%chordalias")
		else
			abc2svg.ch_alias[v[0]] = v[1] || ""
		break
	case "composerspace":
	case "indent":
	case "infospace":
	case "maxstaffsep":
	case "maxsysstaffsep":
	case "musicspace":
	case "partsspace":
	case "staffsep":
	case "subtitlespace":
	case "sysstaffsep":
	case "textspace":
	case "titlespace":
	case "topspace":
	case "vocalspace":
	case "wordsspace":
		f = get_unit(param)	// normally, unit in points - 72 DPI accepted
		if (isNaN(f) || f < 0)
			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":
//	case "botmargin":
	case "leftmargin":
//	case "pageheight":
	case "pagewidth":
	case "rightmargin":
//	case "topmargin":
		f = get_unit(param)	// normally unit in cm or in - 96 DPI
		if (isNaN(f)) {
			syntax(1, errs.bad_val, '%%' + cmd)
			break
		}
		cfmt[cmd] = f;
		img.chg = true
		break
	case "concert-score":
		if (cfmt.sound != "play")
			cfmt.sound = "concert"
		break
	case "writefields":
		set_writefields(param)
		break
	case "volume":
		cmd = "dynamic"
		// fall thru
	case "dynamic":
	case "gchord":
	case "gstemdir":
	case "ornament":
	case "stemdir":
	case "vocal":
		set_pos(cmd, param)
		break
	case "font":
		get_font_scale(param)
		break
	case "fullsvg":
		if (parse.state != 0) {
			syntax(1, errs.not_in_tune, "%%fullsvg")
			break
		}
//fixme: should check only alpha, num and '_' characters
		cfmt[cmd] = param
		break
	case "gracespace":
		v = param.split(/\s+/)
		for (i = 0; i < 3; i++)
			if (isNaN(+v[i])) {
				syntax(1, errs.bad_val, "%%gracespace")
				break
			}
		if (parse.ufmt)
			cfmt[cmd] = new Float32Array(3)
		for (i = 0; i < 3; i++)
			cfmt[cmd][i] = +v[i]
		break
	case "tuplets":
		v = param.split(/\s+/)
		f = v[3]
		if (f)			// if 'where'
			f = posval[f]	// translate the keyword
		if (f)
			v[3] = f
		if (curvoice)
			curvoice.tup = v
		else
			cfmt[cmd] = v
		break
	case "infoname":
	case "partname":
		set_infoname(cmd, param)
		break
	case "notespacingfactor":
		v = param.match(/([.\d]+)[,\s]*(\d+)?/)
		if (v) {
			f = +v[1]
			if (isNaN(f) || f < 1 || f > 2) {
				f = 0
			} else if (v[2]) {
				f2 = +v[2]
				if (isNaN(f))
					f = 0
			} else {
				f2 = cfmt.spatab[5]
			}
		}
		if (!f) {
			syntax(1, errs.bad_val, "%%" + cmd)
			break
		}
		cfmt[cmd] = param		// (for dump)

		// in the table 'spatab',
		// the width of notes is indexed by log2(note_length)
		cfmt.spatab = new Float32Array(10)
		i = 5;				// index of crotchet
		do {
			cfmt.spatab[i] = f2
			f2 /= f
		} while (--i >= 0)
		i = 5;
		f2 = cfmt.spatab[i]
		for ( ; ++i < cfmt.spatab.length; ) {
			f2 *= f;
			cfmt.spatab[i] = f2
		}
		break
	case "play":
		cfmt.sound = "play"		// without clef
		break
	case "pos":
		cmd = param.match(/(\w*)\s+(.*)/)
		if (!cmd || !cmd[2]) {
			syntax(1, "Error in %%pos")
			break
		}
		if (cmd[1].slice(0, 3) == 'tup'		// special case for %%pos tuplet
		 && curvoice) {				// inside tune
			if (!curvoice.tup)
				curvoice.tup = cfmt.tuplets
			else
				curvoice.tup = Object.create(curvoice.tup)
			v = posval[cmd[2]]
			switch (v) {
			case C.SL_ABOVE:
				curvoice.tup[3] = 1
				break
			case C.SL_BELOW:
				curvoice.tup[3] = 2
				break
			case C.SL_HIDDEN:
				curvoice.tup[2] = 1
				break
			}
			break
		}
		if (cmd[1].slice(0, 3) == "vol")
			cmd[1] = "dyn"			// compatibility
		set_pos(cmd[1], cmd[2])
		break
	case "sounding-score":
		if (cfmt.sound != "play")
			cfmt.sound = "sounding"
		break
	case "staffwidth":
		v = get_unit(param)
		if (isNaN(v)) {
			syntax(1, errs.bad_val, '%%' + cmd)
			break
		}
		if (v < 100) {
			syntax(1, "%%staffwidth too small")
			break
		}
		v = cfmt.pagewidth - v - cfmt.leftmargin
		if (v < 2) {
			syntax(1, "%%staffwidth too big")
			break
		}
		cfmt.rightmargin = v;
		img.chg = true
		break
	case "textoption":
		cfmt[cmd] = get_textopt(param)
		break
	case "dynalign":
	case "quiet":
	case "singleline":
	case "stretchlast":
	case "titletrim":
		v = param == '' ? 1 : +param
		if (isNaN(v))
			v = +get_bool(param)
		if (cmd[1] == 't') {		// stretchlast
			if (v < 0 || v > 1) {
				syntax(1, errs.bad_val, '%%' + cmd)
				break
			}
		}
		cfmt[cmd] = v
		break
	case "combinevoices":
		syntax(1, "%%combinevoices is deprecated - use %%voicecombine instead")
		break
	case "voicemap":
		set_v_param("map", param)
		break
	case "voicescale":
		set_v_param("scale", param)
		break
	case "unsizedsvg":
		if (get_bool(param))
			user.imagesize = ""
		else
			delete user.imagesize
		break
	// deprecated
	case "rbdbstop":
		v = get_bool(param)
		if (v && cfmt["abc-version"] >= "2.2")
			cfmt["abc-version"] = "1"
		else if (!v && cfmt["abc-version"] < "2.2")
			cfmt["abc-version"] = "2.2"
		break
	default:		// memorize all global commands
		if (!parse.state)		// (needed for modules)
			cfmt[cmd] = param
		break
	}

	// check if already a same format
	if (sfmt[cmd] && parse.ufmt) {
		// to do...
		parse.ufmt = false
	}
}

// font stuff

// build a font style
function st_font(font) {
    var	n = font.name,
	r = ""

	if (font.weight)
		r += font.weight + " "
	if (font.style)
		r += font.style + " "
	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)
		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) {	// if default font
			font.fid = abc2svg.font_tb.length
			abc2svg.font_tb.push(font)
			if (!font.swfac)
				set_font_fac(font)
			if (!font.pad)
				font.pad = 0
		}

			// set the pointer to the width of the characters
		if (!font.cw_tb) {
			font.cw_tb = !font.name ? ssw_tb
				: font.name.indexOf("ans") > 0
					? ssw_tb		// sans-serif
					: font.name.indexOf("ono") > 0
						? mw_tb		// monospace
						: sw_tb		// serif
		}
		add_fstyle(".f" + font.fid +
			(cfmt.fullsvg || "") +
			"{" + style_font(font) + "}")
		if (font.src)
			add_fstyle("@font-face{\n\
 font-family:" + font.name + ";\n\
 src:" + font.src + "}")
		if (font == cfmt.musicfont)	// add more music font style
			add_fstyle(".f" + font.fid
				+ (cfmt.fullsvg || "")
				+ ' text,tspan{white-space:pre}')
		if (ff.text && !ff.used && font.name.indexOf("text") >= 0) {
			font_style += ff.text	// add font-face's from %%beginsvg
			ff.used = 1 //true
		}
	}
}

// get the font of the 'xxxfont' parameter
function get_font(fn) {
    var	font, font2, fid, st

	fn += "font"
	font = cfmt[fn]
	if (!font) {
		syntax(1, "Unknown font $1", fn)
		return gene.curfont
	}

	if (!font.name || !font.size) {		// if incomplete user font
		font2 = Object.create(gene.deffont)
		if (font.name)
			font2.name = font.name
		if (font.normal) {
			if (font2.weight)	// !! don't use delete !!
				font2.weight = null
			if (font2.style)
				font2.style = null
		}
			if (font.weight)
				font2.weight = font.weight
			if (font.style)
				font2.style = font.style
		if (font.src)
			font2.src = font.src
		if (font.size)
			font2.size = font.size
		st = st_font(font2)
		if (font.class) {
			font2.class = font.class
			st += ' '+ font.class
		}
		fid = abc2svg.font_st[st]
		if (fid != undefined)
			return abc2svg.font_tb[fid]
		abc2svg.font_st[st] = abc2svg.font_tb.length	// will be the font id
		font2.fid = font2.used = undefined
		font = font2
	}
	use_font(font)
	return font
}
// abc2svg - front.js - ABC parsing front-end
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

    var	sav = {},	// save global (between tunes) definitions
	mac = {},	// macros (m:)
	maci = {},	// first letters of macros
	modone = {}	// hooks done by module

// translation table from the ABC draft version 2.2
var abc_utf = {
	"=D": "Đ",
	"=H": "Ħ",
	"=T": "Ŧ",
	"=d": "đ",
	"=h": "ħ",
	"=t": "ŧ",
	"/O": "Ø",
	"/o": "ø",
//	"/D": "Đ",
//	"/d": "đ",
	"/L": "Ł",
	"/l": "ł",
	"vL": "Ľ",
	"vl": "ľ",
	"vd": "ď",
	".i": "ı",
	"AA": "Å",
	"aa": "å",
	"AE": "Æ",
	"ae": "æ",
	"DH": "Ð",
	"dh": "ð",
//	"ng": "ŋ",
	"OE": "Œ",
	"oe": "œ",
	"ss": "ß",
	"TH": "Þ",
	"th": "þ"
}

// accidentals as octal values (abcm2ps compatibility)
var oct_acc = {
	"1": "\u266f",
	"2": "\u266d",
	"3": "\u266e",
	"4": "&#x1d12a;",
	"5": "&#x1d12b;"
}

// convert the escape sequences to utf-8
function cnv_escape(src, flag) {
	var	c, c2,
		dst = "",
		i, j = 0

	while (1) {
		i = src.indexOf('\\', j)
		if (i < 0)
			break
		dst += src.slice(j, i);
		c = src[++i]
		if (!c)
			return dst + '\\'
		switch (c) {
		case '0':
		case '2':
			if (src[i + 1] != '0')
				break
			c2 = oct_acc[src[i + 2]]
			if (c2) {
				dst += c2;
				j = i + 3
				continue
			}
			break
		case 'u':
			j = Number("0x" + src.slice(i + 1, i + 5));
			if (isNaN(j) || j < 0x20) {
				dst += src[++i] + "\u0306"	// breve accent
				j = i + 1
				continue
			}
			c = String.fromCharCode(j)
			if (c == '\\') {
				i += 4
				break
			}
			dst += c
			j = i + 5
			continue
		case 't':			// TAB
			dst += '\t';
			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;
				j = i + 2
				continue
			}

			// try unicode combine characters
			c2 = src[i + 1]
			if (!c2)
				break	// !! the next test is true if c2 is undefined !!
			if (!/[A-Za-z]/.test(c2))
				break
			switch (c) {
			case '`':
				dst += c2 + "\u0300"	// grave
				j = i + 2
				continue
			case "'":
				dst += c2 + "\u0301"	// acute
				j = i + 2
				continue
			case '^':
				dst += c2 + "\u0302"	// circumflex
				j = i + 2
				continue
			case '~':
				dst += c2 + "\u0303"	// tilde
				j = i + 2
				continue
			case '=':
				dst += c2 + "\u0304"	// macron
				j = i + 2
				continue
			case '_':
				dst += c2 + "\u0305"	// overline
				j = i + 2
				continue
			case '.':
				dst += c2 + "\u0307"	// dot
				j = i + 2
				continue
			case '"':
				dst += c2 + "\u0308"	// dieresis
				j = i + 2
				continue
			case 'o':
				dst += c2 + "\u030a"	// ring
				j = i + 2
				continue
			case 'H':
				dst += c2 + "\u030b"	// hungarumlaut
				j = i + 2
				continue
			case 'v':
				dst += c2 + "\u030c"	// caron
				j = i + 2
				continue
//			case ',':
//				dst += c2 + "\u0326"	// comma below
//				j = i + 2
//				continue
			case 'c':
				dst += c2 + "\u0327"	// cedilla
				j = i + 2
				continue
			case ';':
				dst += c2 + "\u0328"	// ogonek
				j = i + 2
				continue
			}
			break
		}
		if (flag == 'w')	// if lyrics line (w:)
			dst += '\\'	// keep the backslash
		dst += c
		j = i + 1
	}
	return dst + src.slice(j)
}

// ABC include
var include = 0

function do_include(fn) {
	var file, parse_sav

	if (!user.read_file) {
		syntax(1, "No read_file support")
		return
	}
	if (include > 2) {
		syntax(1, "Too many include levels")
		return
	}
	file = user.read_file(fn)
	if (!file) {
		syntax(1, "Cannot read file '$1'", fn)
		return
	}
	include++;
	parse_sav = clone(parse);
	tosvg(fn, file);
	parse_sav.state = parse.state;
	parse_sav.ckey = parse.ckey
	parse = parse_sav;
	include--
}

// parse ABC code
function tosvg(in_fname,		// file name
		file,			// file content
		bol, eof) {		// beginning/end of file
	var	i, c, eol, end,
		select,
		line0, line1,
		last_info, opt, text, a, b, s,
		pscom,
		txt_add = '\n'		// for "+:"

	// check if a tune is selected
	function tune_selected() {
		var	re, res,
			i = file.indexOf('K:', bol)

		if (i < 0) {
//			syntax(1, "No K: in tune")
			return false
		}
		i = file.indexOf('\n', i)
		if (parse.select.test(file.slice(parse.bol, i)))
			return true
		re = /\n\w*\n/;
		re.lastIndex = i;
		res = re.exec(file)
		if (res)
			eol = re.lastIndex
		else
			eol = eof
		return false
	} // tune_selected()

	// remove the comment at end of text
	// if flag, handle the escape sequences
	// if flag is 'w' (lyrics), keep the '\'s
	function uncomment(src, flag) {
		if (!src)
			return src
	    var	i = src.indexOf('%')
		if (i == 0)
			return ''
		if (i > 0)
			src = src.replace(/([^\\])%.*/, '$1')
				 .replace(/\\%/g, '%');
		src = src.replace(/\s+$/, '')
		if (flag && src.indexOf('\\') >= 0)
			return cnv_escape(src, flag)
		return src
	} // uncomment()

	// set the sequence showing the source and save it in sav.src
	function set_src(stag, se) {
	    var	r, t,
		etag = ""

		if (!se)
			se = file.indexOf('\n\n', bol)	// end of tune
		if (se < 0)
			se = eof
		if (typeof stag != "object") {		// set the tag after source
			if (stag[0] != 'b' && stag[0] != 'a' && stag[0] != '+'
			 && stag[0] != '*')
				stag = 'b' + stag	// default: source before
			if (stag[1] != '<')		// (if bool)
				stag = stag[0] + "<pre>"
			r = stag.match(/<\/?[^>]*>/g)
			while (1) {
				t = r.pop()
				if (!t)
					break
				if (t[1] == '/' || t.slice(-2) == '/>')
					r.pop()		// skip this stop/start tag
				else
					etag += '</' + t.slice(1)
			}
			cfmt.show_source = stag = [stag, etag]
		}
		t = stag[0].slice(1)
			+ clean_txt(file.slice(bol, se))
			+ stag[1]
		if (stag[0][0] == '+' && sav.src)
			sav.src += t
		else
			sav.src = t
	} // set_src()

	function end_tune() {
		parse.bol = bol				// (for multi V:)
		generate()
		cfmt = sav.cfmt;
		info = sav.info;
		char_tb = sav.char_tb;
		glovar = sav.glovar;
		maps = sav.maps;
		mac = sav.mac;
		maci = sav.maci;
		parse.tune_v_opts = null;
		parse.scores = null;
		parse.ufmt = false
		delete parse.pq
		init_tune()
		img.chg = true;
		set_page();
		if (cfmt.show_source) {
			user.img_out("</div>")
			if (cfmt.show_source[0][0] == 'a')
				user.img_out(sav.src)
		}
	} // end_tune()

	// get %%voice
	function do_voice(select, in_tune) {
	    var	opt, bol
		if (select == "end")
			return		// end of previous %%voice

		// get the options
		if (in_tune) {
			if (!parse.tune_v_opts)
				parse.tune_v_opts = {};
			opt = parse.tune_v_opts
		} else {
			if (!parse.voice_opts)
				parse.voice_opts = {};
			opt = parse.voice_opts
		}
		opt[select] = []
		while (1) {
			bol = ++eol
			if (file[bol] != '%')
				break
			eol = file.indexOf('\n', eol);
			if (file[bol + 1] != line1)
				continue
			bol += 2
			if (eol < 0)
				text = file.slice(bol)
			else
				text = file.slice(bol, eol);
			a = text.match(/\S+/)
			switch (a[0]) {
			default:
				opt[select].push(uncomment(text, true))
				continue
			case "score":
			case "staves":
			case "tune":
			case "voice":
				bol -= 2
				break
			}
			break
		}
		eol = parse.eol = bol - 1
	} // do_voice()

	// apply the options to the current tune
	function tune_filter() {
	    var	o, opts, j, pc, h,
		i = file.indexOf('K:', bol)

		i = file.indexOf('\n', i);
		h = file.slice(parse.bol, i)	// tune header

		for (i in parse.tune_opts) {
			if (!parse.tune_opts.hasOwnProperty(i))
				continue
			if (!(new RegExp(i)).test(h))
				continue
			opts = parse.tune_opts[i]
			for (j = 0; j < opts.t_opts.length; j++) {
				pc = opts.t_opts[j]
				switch (pc.match(/\S+/)[0]) {
				case "score":
				case "staves":
					if (!parse.scores)
						parse.scores = [];
					parse.scores.push(pc)
					break
				default:
					self.do_pscom(pc)
					break
				}
			}
			opts = opts.v_opts
			if (!opts)
				continue
			for (j in opts) {
				if (!opts.hasOwnProperty(j))
					continue
				if (!parse.tune_v_opts)
					parse.tune_v_opts = {};
				if (!parse.tune_v_opts[j])
					parse.tune_v_opts[j] = opts[j]
				else
					parse.tune_v_opts[j] =
						parse.tune_v_opts[j].
								concat(opts[j])
			}
		}
	} // tune_filter()

	// export functions and/or set module hooks
	if (abc2svg.mhooks) {
		for (i in abc2svg.mhooks) {
			if (!modone[i]) {
				modone[i] = 1 //true
				abc2svg.mhooks[i](self)
			}
		}
	}

	// initialize
	parse.file = file;		// used for errors
	parse.fname = in_fname

	// scan the file
	if (bol == undefined)
		bol = 0
	if (!eof)
		eof = file.length
	if (file.slice(bol, bol + 5) == "%abc-")
		cfmt["abc-version"] = /[1-9.]+/.exec(file.slice(bol + 5, bol + 10))
	for ( ; bol < eof; bol = parse.eol + 1) {
		eol = file.indexOf('\n', bol)	// get a line
		if (eol < 0 || eol > eof)
			eol = eof;
		parse.eol = eol

		// remove the ending white spaces
		while (1) {
			eol--
			switch (file[eol]) {
			case ' ':
			case '\t':
				continue
			}
			break
		}
		eol++
		if (eol == bol) {		// empty line
			if (parse.state == 1) {
				parse.istart = bol;
				syntax(1, "Empty line in tune header - ignored")
			} else if (parse.state >= 2) {
				end_tune()
				parse.state = 0
				if (parse.select) {	// skip to next tune
					eol = file.indexOf('\nX:', parse.eol)
					if (eol < 0)
						eol = eof
					parse.eol = eol
				}
			}
			continue
		}
		parse.istart = parse.bol = bol;
		parse.iend = eol;
		parse.line.index = 0;

		// check if the line is a pseudo-comment or I:
		line0 = file[bol];
		line1 = file[bol + 1]
		if ((line0 == 'I' && line1 == ':')
		  || line0 == '%') {
			if (line0 == '%' && parse.prefix.indexOf(line1) < 0)
				continue		// comment

			// change "%%abc xxxx" to "xxxx"
			if (file[bol + 2] == 'a'
			 && file[bol + 3] == 'b'
			 && file[bol + 4] == 'c'
			 && file[bol + 5] == ' ') {
				bol += 6;
				line0 = file[bol];
				line1 = file[bol + 1]
			} else {
				pscom = true
			}
		}

		// pseudo-comments
		if (pscom) {
			pscom = false;
			bol += 2		// skip %%/I:
			text = file.slice(bol, eol)
			a = text.match(/([^\s]+)\s*(.*)/)
			if (!a || a[1][0] == '%')
				continue
			switch (a[1]) {
			case "abcm2ps":
			case "ss-pref":
				parse.prefix = a[2]	// may contain a '%'
				continue
			case "abc-include":
				do_include(uncomment(a[2]))
				continue
			}

			// beginxxx/endxxx
			if (a[1].slice(0, 5) == 'begin') {
				b = a[1].substr(5);
				end = '\n' + line0 + line1 + "end" + b;
				i = file.indexOf(end, eol)
				if (i < 0) {
					syntax(1, "No $1 after %%$2",
							end.slice(1), a[1]);
					parse.eol = eof
					continue
				}
				self.do_begin_end(b, uncomment(a[2]),
					file.slice(eol + 1, i)
						.replace(/\n%[^%].*$/gm,'')
						.replace(/^%%/gm,''))
				parse.eol = file.indexOf('\n', i + 6)
				if (parse.eol < 0)
					parse.eol = eof
				continue
			}
			switch (a[1]) {
			case "show_source":
				b = uncomment(a[2])
				switch (b[0]) {
				case '*':
					i = file.indexOf('\n' + line0 + line1
							+ "show_source", eol)
					bol -= 2	// keep %%show_.. in the source
					set_src(b, i)
					user.img_out(sav.src)
					// fall thru
				case '0':
					b = ""
					// fall thru
				default:
					cfmt[a[1]] = b
					// fall thru
				}
				continue
			case "select":
				if (parse.state != 0) {
					syntax(1, errs.not_in_tune, "%%select")
					continue
				}
				select = uncomment(a[2])
				if (select[0] == '"')
					select = select.slice(1, -1);
				if (!select) {
					delete parse.select
					continue
				}
				select = select.replace(/\(/g, '\\(');
				select = select.replace(/\)/g, '\\)');
//				select = select.replace(/\|/g, '\\|');
				parse.select = new RegExp(select, 'm')
				continue
			case "tune":
				if (parse.state != 0) {
					syntax(1, errs.not_in_tune, "%%tune")
					continue
				}
				select = uncomment(a[2])

				// if void %%tune, free all tune options
				if (!select) {
					parse.tune_opts = {}
					continue
				}
				
				if (select == "end")
					continue	// end of previous %%tune

				if (!parse.tune_opts)
					parse.tune_opts = {};
				parse.tune_opts[select] = opt = {
						t_opts: []
//						v_opts: {}
					};
				while (1) {
					bol = eol
					if (file[bol + 1] != '%')
						break
					eol = file.indexOf('\n', eol + 1)
					if (file[bol + 2] != line1)
						continue
					text = file.slice(bol + 3,
							eol < 0 ? undefined : eol)
					a = text.match(/([^\s]+)\s*(.*)/)
					switch (a[1]) {
					case "tune":
						break
					case "voice":
						do_voice(uncomment(a[2],
								true), true)
						continue
					default:
						opt.t_opts.push(
							uncomment(text, true))
						continue
					}
					break
				}
				if (parse.tune_v_opts) {
					opt.v_opts = parse.tune_v_opts;
					parse.tune_v_opts = null
				}
				parse.eol = bol
				continue
			case "voice":
				if (parse.state != 0) {
					syntax(1, errs.not_in_tune, "%%voice")
					continue
				}
				select = uncomment(a[2])

				/* if void %%voice, free all voice options */
				if (!select) {
					parse.voice_opts = null
					continue
				}
				
				do_voice(select)
				continue
			}
			self.do_pscom(uncomment(text, true))
			continue
		}

		// 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))
			if (parse.line.buffer)
				parse_music_line()
			continue
		}

		// information fields
		bol += 2
		while (1) {
			switch (file[bol]) {
			case ' ':
			case '\t':
				bol++
				continue
			}
			break
		}
		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)
				continue
			}
			if (parse.select
			 && !tune_selected()) {	// skip to the next tune
				eol = file.indexOf('\nX:', parse.eol)
				if (eol < 0)
					eol = eof;
				parse.eol = eol
				continue
			}

			sav.cfmt = clone(cfmt);
			sav.info = clone(info, 2)	// (level 2 for info.V[])
			sav.char_tb = clone(char_tb);
			sav.glovar = clone(glovar);
			sav.maps = clone(maps, 1);
			sav.mac = clone(mac);
			sav.maci = clone(maci);
			if (cfmt.show_source) {
				bol -= 2
				set_src(cfmt.show_source)
				if (cfmt.show_source[0][0] == 'b')
					user.img_out(sav.src)
				user.img_out('<div class="source">')
			}
			info.X = text;
			parse.state = 1			// tune header
			if (parse.tune_opts)
				tune_filter()
			continue
		case 'T':
			switch (parse.state) {
			case 0:
				continue
			case 1:
			case 2:
				text = trim_title(text, info.T)
				if (info.T == undefined)	// (keep empty T:)
					info.T = text
				else
					info.T += "\n" + text
				continue
			}
			s = new_block("title");
			s.text = text
			continue
		case 'K':
			switch (parse.state) {
			case 0:
				continue
			case 1:				// tune header
				info.K = text
				break
			}
			do_info(line0, text)
			continue
		case 'W':
			if (parse.state == 0
			 || cfmt.writefields.indexOf(line0) < 0)
				break
			if (info.W == undefined)
				info.W = text
			else
				info.W += txt_add + text
			break

		case 'm':
			if (parse.state >= 2) {
				syntax(1, errs.ignored, line0)
				continue
			}
			a = text.match(/(.*?)[= ]+(.*)/)
			if (!a || !a[2]) {
				syntax(1, errs.bad_val, "m:")
				continue
			}
			mac[a[1]] = a[2];
			maci[a[1][0]] = true	// first letter
			break

		// info fields in tune body only
		case 's':
			if (parse.state != 3
			 || cfmt.writefields.indexOf(line0) < 0)
				break
			get_sym(text, txt_add == ' ')
			break
		case 'w':
			if (parse.state != 3
			 || cfmt.writefields.indexOf(line0) < 0)
				break
			get_lyrics(text, txt_add == ' ')
			break
		case '|':			// "|:" starts a music line
			if (parse.state < 2)
				continue
			parse.line.buffer = text
			parse_music_line()
			continue
		default:
			if ("ABCDFGHNOSZ".indexOf(line0) >= 0) {
				if (parse.state >= 2) {
					syntax(1, errs.ignored, line0)
					continue
				}
//				if (cfmt.writefields.indexOf(c) < 0)
//					break
				if (!info[line0])
					info[line0] = text
				else
					info[line0] += txt_add + text
				break
			}

			// info field which may be embedded
			do_info(line0, text)
			continue
		}
		txt_add = '\n';
		last_info = line0
	}
	if (include)
		return
	if (parse.state == 1) {
		syntax(1, "End of file in tune header")
		get_key("C")
	}
	if (parse.state >= 2)
		end_tune();
	if (sav.src && cfmt.show_source[0] == '+') {
		user.img_out(sav.src)		// source of all tunes
		sav.src = null
	}
	parse.state = 0
}
Abc.prototype.tosvg = tosvg
// abc2svg - music.js - music generation
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

var	gene,
	staff_tb,
	nstaff,			// current number of staves
	tsnext,			// next line when cut
	realwidth,		// real staff width while generating
	insert_meter,		// insert the time signature
	spf_last,		// spacing for last short line

	smallest_duration

/* -- decide whether to shift heads to other side of stem on chords -- */
/* this routine is called only once per tune */

// distance for no overlap - index: [prev acc][cur acc]
//var dt_tb = [
//	[5, 5, 5, 5],		/* dble sharp */
//	[5, 6, 6, 6],		/* sharp */
//	[5, 6, 5, 6],		/* natural */
//	[5, 5, 5, 5]		/* flat / dble flat */
//]

// accidental x offset - index = note head type
var dx_tb = new Float32Array([
	9.5,		// FULL
	9.5,		// EMPTY
	13,		// OVAL
	16,		// OVALBARS
	16		// SQUARE
])

// head width  - index = note head type
var hw_tb = new Float32Array([
	4.7,		// FULL
	5,		// EMPTY
	6,		// OVAL
	7.2,		// OVALBARS
	7.5		// SQUARE
])

/* head width for voice overlap - index = note head type */
var w_note = new Float32Array([
	2.5,		// FULL
	3,		// EMPTY
	4.5,		// OVAL
	6,		// OVALBARS
	6.5		// SQUARE
])

// get head type, dots, flags of note/rest for a duration
function identify_note(s, dur_o) {
    var	r = abc2svg.hdn[dur_o]
	if (r)				// in cache?
		return r

    var	head, flags,
	dots = 0,
	dur = dur_o

	if (dur % 12 != 0)
		error(1, s, "Invalid note duration $1", dur);
	dur /= 12			/* see C.BLEN for values */
	if (!dur)
		error(1, s, "Note too short")
	for (flags = 5; dur; dur >>= 1, flags--) {
		if (dur & 1)
			break
	}
	dur >>= 1
	while (dur >> dots > 0)
		dots++

	flags -= dots
	if (flags >= 0) {
		head = C.FULL
	} else switch (flags) {
	default:
		error(1, s, "Note too long")
		flags = -4
		/* fall thru */
	case -4:
		head = C.SQUARE
		break
	case -3:
		head = s.fmt.squarebreve ? C.SQUARE : C.OVALBARS
		break
	case -2:
		head = C.OVAL
		break
	case -1:
		head = C.EMPTY
		break
	}
	abc2svg.hdn[dur_o] =
		r = [head, dots, flags]
	return r
}

function set_head_shift(s) {
	var	i, i1, i2, d, ps, dx,
		dx_head = dx_tb[s.head],
		dir = s.stem,
		n = s.nhd

	if (!n)
		return			// single note

	/* set the head shifts */
	dx = dx_head * .74
	if (s.grace)
		dx *= .6
	if (dir >= 0) {
		i1 = 1;
		i2 = n + 1;
		ps = s.notes[0].pit
	} else {
		dx = -dx;
		i1 = n - 1;
		i2 = -1;
		ps = s.notes[n].pit
	}
	var	shift = false,
		dx_max = 0
	for (i = i1; i != i2; i += dir) {
		d = s.notes[i].pit - ps;
		ps = s.notes[i].pit
		if (!d) {
			if (shift) {		/* unison on shifted note */
				var new_dx = s.notes[i].shhd =
						s.notes[i - dir].shhd + dx
				if (dx_max < new_dx)
					dx_max = new_dx
				continue
			}
			if (i + dir != i2	/* second after unison */
//fixme: should handle many unisons after second
			 && ps + dir == s.notes[i + dir].pit) {
				s.notes[i].shhd = -dx
				if (dx_max < -dx)
					dx_max = -dx
				continue
			}
		}
		if (d < 0)
			d = -d
		if (d > 3 || (d >= 2 && s.head != C.SQUARE)) {
			shift = false
		} else {
			shift = !shift
			if (shift) {
				s.notes[i].shhd = dx
				if (dx_max < dx)
					dx_max = dx
			}
		}
	}
	s.xmx = dx_max				/* shift the dots */
}

// set the accidental shifts for a set of chords
// @grh = head width when grace notes
function acc_shift(notes, grh) {
    var	i, i1, i2, dx, dx1, dx2, ps, p1, acc, dxh,
	n = notes.length

	// set the shifts from the head shifts
	for (i = n - 1; --i >= 0; ) {	// (no shift on top)
		dx = notes[i].shhd
		if (!dx || dx > 0)
			continue
//fixme: '9' is the width of a quarter note
		dx = (grh || (notes[i].s ? dx_tb[notes[i].s.head] : 9))
			- dx
		ps = notes[i].pit
		for (i1 = n; --i1 >= 0; ) {
			if (!notes[i1].acc)
				continue
			p1 = notes[i1].pit
			if (p1 < ps - 3)
				break
			if (p1 > ps + 3)
				continue
			if (notes[i1].shac < dx)
				notes[i1].shac = dx
		}
	}

	// set the shifts of the highest and lowest notes
	for (i1 = n; --i1 >= 0; ) {
		if (notes[i1].acc) {
			p1 = notes[i1].pit	// top note with accidental
			dx1 = notes[i1].shac
			if (!dx1) {
				dx1 = notes[i1].shhd
				dxh = grh || dx_tb[notes[i1].s.head]
				if (dx1 < 0)
					dx1 = dxh - dx1
				else
					dx1 = dxh
			}
			break
		}
	}
	if (i1 < 0)				// no accidental
		return
	for (i2 = 0; i2 < i1; i2++) {
		if (notes[i2].acc) {
			ps = notes[i2].pit	// bottom note with accidental
			dx2 = notes[i2].shac
			if (!dx2) {
				dx2 = notes[i2].shhd
				dxh = grh || dx_tb[notes[i2].s.head]
				if (dx2 < 0)
					dx2 = dxh - dx2
				else
					dx2 = dxh
			}
			break
		}
	}
	if (i1 == i2) {			// only one accidental
		notes[i1].shac = dx1
		return
	}

	if (p1 > ps + 4) {		// if interval greater than a sixth
		if (dx1 > dx2)
			dx2 = dx1	// align the accidentals
		notes[i1].shac = notes[i2].shac = dx2
	} else {
		notes[i1].shac = dx1
		if (notes[i1].pit != notes[i2].pit
		 || notes[i1].acc != notes[i2].acc)
			dx1 += 7
		notes[i2].shac = dx2 = dx1
	}
	dx2 += 7

	// shift the remaining accidentals
	for (i = i1; --i > i2; ) {		// from top to bottom
		acc = notes[i].acc
		if (!acc)
			continue
		dx = notes[i].shac
		if (dx < dx2)
			dx = dx2
		ps = notes[i].pit
		for (i1 = n; --i1 > i; ) {
			if (!notes[i1].acc)
				continue
			p1 = notes[i1].pit
			if (p1 >= ps + 4) {	// pitch far enough
				if (p1 > ps + 4	// if more than a fifth
				 || acc < 0	// if flat/dble flat
				 || notes[i1].acc < 0)
					continue
			}
			if (dx > notes[i1].shac - 6) {
				dx1 = notes[i1].shac + 7
				if (dx1 > dx)
					dx = dx1
			}
		}
		notes[i].shac = dx
	}
}

/* set the horizontal shift of accidentals */
/* this routine is called only once per tune */
function set_acc_shft() {
    var	s, s2, st, i, acc, st, t, notes

	// search the notes with accidentals at the same time
	s = tsfirst
	while (s) {
		if (s.type != C.NOTE
		 || s.invis) {
			s = s.ts_next
			continue
		}
		st = s.st;
		t = s.time;
		acc = false
		for (s2 = s; s2; s2 = s2.ts_next) {
			if (s2.time != t
			 || s2.type != C.NOTE
			 || s2.st != st)
				break
			for (i = 0; i <= s2.nhd; i++) {
				if (s2.notes[i].acc) {
					s2.notes[i].s = s2
					acc = true
				}
			}
		}
		if (!acc) {
			s = s2
			continue
		}

		// build a pseudo chord and shift the accidentals
		notes = []
		for ( ; s != s2; s = s.ts_next) {
			if (!s.invis)
				Array.prototype.push.apply(notes, s.notes)
		}
		notes.sort(abc2svg.pitcmp)
		acc_shift(notes)
	}
}

// link a symbol before an other one
function lkvsym(s, next) {	// voice linkage
	s.next = next;
	s.prev = next.prev
	if (s.prev)
		s.prev.next = s
	else
		s.p_v.sym = s;
	next.prev = s
}
function lktsym(s, next) {	// time linkage
    var	old_wl

	s.ts_next = next
	if (next) {
		s.ts_prev = next.ts_prev
		if (s.ts_prev)
			s.ts_prev.ts_next = s;
		next.ts_prev = s
	} else {
//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])
	if (!next || next.seqst)
		return
	next.seqst = next.time != s.time ||
			(w_tb[s.type] != w_tb[next.type]
			 && !!w_tb[s.type])
	if (next.seqst) {
		old_wl = next.wl
		self.set_width(next)
		if (next.a_ly)
			ly_set(next)
		if (!next.shrink) {
			next.shrink = next.wl
			if (next.prev)
				next.shrink += next.prev.wr
		} else {
			next.shrink += next.wl - old_wl
		}
		next.space = 0
	}
}

/* -- unlink a symbol -- */
function unlksym(s) {
	if (s.next)
		s.next.prev = s.prev
	if (s.prev)
		s.prev.next = s.next
	else
		s.p_v.sym = s.next
	if (s.ts_next) {
		if (s.seqst) {
		    if (s.ts_next.seqst) {
			s.ts_next.shrink += s.shrink;
			s.ts_next.space += s.space
		    } 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
	if (tsfirst == s)
		tsfirst = s.ts_next
	if (tsnext == s)
		tsnext = s.ts_next
}

/* -- insert a clef change (treble or bass) before a symbol -- */
function insert_clef(s, clef_type, clef_line) {
	var	p_voice = s.p_v,
		new_s,
		st = s.st

	/* don't insert the clef between two bars */
	if (s.type == C.BAR && s.prev && s.prev.type == C.BAR
	 && s.prev.bar_type[0] != ':')
		s = s.prev;

	/* create the symbol */
	p_voice.last_sym = s.prev
	if (!p_voice.last_sym)
		p_voice.sym = null;
	p_voice.time = s.time;
	new_s = sym_add(p_voice, C.CLEF);
	new_s.next = s;
	s.prev = new_s;

	new_s.clef_type = clef_type;
	new_s.clef_line = clef_line;
	new_s.st = st;
	new_s.clef_small = true
	delete new_s.second;
	new_s.notes = []
	new_s.notes[0] = {
		pit: s.notes[0].pit
	}
	new_s.nhd = 0;

	/* link in time */
	while (!s.seqst)
		s = s.ts_prev;
	lktsym(new_s, s)
	if (s.soln) {			// move the start of line
		new_s.soln = true
		delete s.soln
	}
	return new_s
}

/* -- set the staff of the floating voices -- */
/* this function is called only once per tune */
function set_float() {
	var p_voice, st, staff_chg, v, s, s1, up, down

	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
//		if (!p_voice.floating)
//			continue
		staff_chg = false;
		st = p_voice.st
		for (s = p_voice.sym; s; s = s.next) {
			if (!s.floating) {
				while (s && !s.floating)
					s = s.next
				if (!s)
					break
				staff_chg = false
			}
			if (!s.dur) {
				if (staff_chg)
					s.st++
				continue
			}
			if (s.notes[0].pit >= 19) {		/* F */
				staff_chg = false
				continue
			}
			if (s.notes[s.nhd].pit <= 12) {	/* F, */
				staff_chg = true
				s.st++
				continue
			}
			up = 127
			for (s1 = s.ts_prev; s1; s1 = s1.ts_prev) {
				if (s1.st != st
				 || s1.v == s.v)
					break
				if (s1.type == C.NOTE)
				    if (s1.notes[0].pit < up)
					up = s1.notes[0].pit
			}
			if (up == 127) {
				if (staff_chg)
					s.st++
				continue
			}
			if (s.notes[s.nhd].pit > up - 3) {
				staff_chg = false
				continue
			}
			down = -127
			for (s1 = s.ts_next; s1; s1 = s1.ts_next) {
				if (s1.st != st + 1
				 || s1.v == s.v)
					break
				if (s1.type == C.NOTE)
				    if (s1.notes[s1.nhd].pit > down)
					down = s1.notes[s1.nhd].pit
			}
			if (down == -127) {
				if (staff_chg)
					s.st++
				continue
			}
			if (s.notes[0].pit < down + 3) {
				staff_chg = true
				s.st++
				continue
			}
			up -= s.notes[s.nhd].pit
			down = s.notes[0].pit - down
			if (!staff_chg) {
				if (up < down + 3)
					continue
				staff_chg = true
			} else {
				if (up < down - 3) {
					staff_chg = false
					continue
				}
			}
			s.st++
		}
	}
}

/* -- set the x offset of the grace notes -- */
function set_graceoffs(s) {
	var	next, m, dx, x,
		gspleft = s.fmt.gracespace[0],
		gspinside = s.fmt.gracespace[1],
		gspright = s.fmt.gracespace[2],
		g = s.extra;

	if (s.prev && s.prev.type == C.BAR)
		gspleft -= 3;
	x = gspleft;

	g.beam_st = true
	for ( ; ; g = g.next) {
		set_head_shift(g)
		acc_shift(g.notes, 6.5)
		dx = 0
		for (m = g.nhd; m >= 0; m--) {
			if (g.notes[m].shac - 2 > dx)
				dx = g.notes[m].shac - 2
		}
		x += dx;
		g.x = x

		if (g.nflags <= 0)
			g.beam_st = g.beam_end = true
		next = g.next
		if (!next) {
			g.beam_end = true
			break
		}
		if (next.nflags <= 0)
			g.beam_end = true
		if (g.beam_end) {
			next.beam_st = true;
			x += gspinside / 4
		}
		if (g.nflags <= 0)
			x += gspinside / 4
		if (g.y > next.y + 8)
			x -= 1.5
		x += gspinside
	}

	next = s.next
	if (next
	 && next.type == C.NOTE) {	/* if before a note */
		if (g.y >= 3 * (next.notes[next.nhd].pit - 18))
			gspright -= 1		// above, a bit closer
		else if (g.beam_st
		      && g.y < 3 * (next.notes[next.nhd].pit - 18) - 4)
			gspright += 2		// below with flag, a bit further
	}
	x += gspright;

	/* return the whole width */
	return x
}

// Compute the smallest spacing between symbols according to chord symbols
//	so that they stay at the same offset
// and, also, adjust the spacing due to the lyric words.
// Constraints:
// - assume the chord symbols are only in the first staff
// - treat only the first chord symbol of each symbol
// - the chord symbols under the staff are ignored
function set_w_chs(s) {
    var	i, ch, w0, s0, dw,
	x = 0,
	n = 0

	set_font("vocal")
	for ( ; s; s = s.ts_next) {
		if (s.seqst) {
			x += s.shrink;
			n++
		}
		if (s.a_ly)			// if some lyric
			ly_set(s)

		if (!s.a_gch)
			continue
		for (i = 0; i < s.a_gch.length; i++) {
			ch = s.a_gch[i]
			if (ch.type != 'g' || ch.y < 0) // upper chord symbol only
				continue
			if (w0) {		// width of the previous chord symbol
				if (w0 > x + ch.x) {
					if (s.prev // (if not at start of a secondary voice)
					 && s.prev.seqst
					 && s.prev.type == C.BAR) // don't move away
						n--		// the symbol from a bar
					dw = (w0 - x - ch.x) / n
					while (1) {
						s0 = s0.ts_next
						if (s0.shrink)
							s0.shrink += dw
						if (s0 == s
						 || s0.type == C.BAR)
							break
					}
				}
			}
			s0 = s;
			w0 = ch.text.wh[0];
			n = 0;
//			x = ch.font.box ? -2 : 0
			x = 0
			break
		}
	}
}

// compute the width needed by the left and right annotations
function gchord_width(s, wlnote, wlw) {
    var	gch, w, ix,
	arspc = 0

	for (ix = 0; ix < s.a_gch.length; ix++) {
		gch = s.a_gch[ix]
		switch (gch.type) {
		case '<':		/* left */
			w = gch.text.wh[0] + wlnote
			if (w > wlw)
				wlw = w
			break
		case '>':		/* right */
			w = gch.text.wh[0] + s.wr
			if (w > arspc)
				arspc = w
			break
		}
	}
	if (s.wr < arspc)
		s.wr = arspc

	return wlw
}

/* -- set the width of a symbol -- */
/* This routine sets the minimal left and right widths wl,wr
 * so that successive symbols are still separated when
 * no extra glue is put between them */
// (possible hook)
Abc.prototype.set_width = function(s) {
    var	s2, i, m, xx, w, wlnote, wlw, acc, nt,
	bar_type, meter, last_acc, n1, n2, esp, tmp

	if (s.play) {			// if play symbol
		s.wl = s.wr = 0
		return
	}

	switch (s.type) {
	case C.NOTE:
	case C.REST:

		/* set the note widths */
		s.wr = wlnote = s.invis ? 0 : hw_tb[s.head]

		/* room for shifted heads and accidental signs */
		if (s.xmx > 0)
			s.wr += s.xmx + 4;
		for (s2 = s.prev; s2; s2 = s2.prev) {
			if (w_tb[s2.type])
				break
		}
		if (s2) {
			switch (s2.type) {
			case C.BAR:
			case C.CLEF:
			case C.KEY:
			case C.METER:
				wlnote += 3
				break
			case C.STBRK:
				wlnote += 8
				break
			case C.NOTE:

				// change the spacing when stems in reverse directions
				if (s.stem * s2.stem < 0)
					wlnote += s.stem < 0 ? 3 : -3
				break
			}
		}
		w = 0				// width of the chord decorations
		for (m = 0; m <= s.nhd; m++) {
			nt = s.notes[m]
			xx = nt.shhd
			if (xx < 0) {
				if (wlnote < -xx + 5)
					wlnote = -xx + 5
			}
			acc = nt.acc
			if (acc
			 && (!s2
			  || s2.type != C.NOTE
			  || (s2.stem >= 0 && s2.notes[0].pit < nt.pit + 4)
			  || (s2.stem < 0 && s2.notes[s2.nhd].pit > nt.pit - 4))) {
				tmp = nt.shac +
					(typeof acc == "object" ? 5.5 : 3.5)
				if (s2 && s2.stem < 0
				 && s2.notes[s2.nhd].pit > nt.pit + 4)
					tmp -= 5
				if (wlnote < tmp)
					wlnote = tmp
			}
			if (nt.a_dd) {		// if decoration in chord
				tmp = deco_wch(nt)
				if (w < tmp)
					w = tmp
			}
		}
		wlnote += w
		if (s2) {
			switch (s2.type) {
			case C.BAR:
			case C.CLEF:
			case C.KEY:
			case C.METER:
				wlnote -= 3
				break
			}
		}

		/* room for the decorations */
		if (s.a_dd)
			wlnote = deco_width(s, wlnote)

		/* space for flag if stem goes up on standalone note */
		if (s.beam_st && s.beam_end
		 && s.stem > 0 && s.nflags > 0) {
			if (s.wr < s.xmx + 9)
				s.wr = s.xmx + 9
		}

		/* leave room for dots and set their offset */
		if (s.dots) {
		  if (s.wl == undefined)	// don't recompute if new music line
			switch (s.head) {
			case C.SQUARE:
			case C.OVALBARS:
				s.xmx += 3
				break
			case C.OVAL:
				s.xmx += 1
				break
			}
			if (s.wr < s.xmx + 8)
				s.wr = s.xmx + 8
			if (s.dots >= 2)
				s.wr += 3.5 * (s.dots - 1)
		}

		/* if a tremolo on 2 notes, have space for the small beam(s) */
		if (s.trem2 && s.beam_end
		 && wlnote < 20)
			wlnote = 20

		wlw = wlnote

		if (s2) {
			switch (s2.type) {
			case C.NOTE:

				/* make sure helper lines don't overlap */
				if ((s.y > 27 && s2.y > 27)
				 || (s.y < -3 && s2.y < -3)) {
					if (wlw < 6)
						wlw = 6
				}

				/* have ties wide enough */
				if (s2.tie) {
					if (wlw < 14)
						wlw = 14
				}
				break
			case C.CLEF:		/* extra space at start of line */
				if (s2.second
				 || s2.clef_small)
					break
				// fall thru
			case C.KEY:
				if (s.a_gch)
					wlw += 4 // have some room for the chord symbols
				// fall thru
			case C.METER:
				wlw += 3
				break
			}
		}

		/* leave room for guitar chord */
		if (s.a_gch)
			wlw = gchord_width(s, wlnote, wlw)

		// ignore the lyrics for now

		/* if preceeded by a grace note sequence, adjust */
		if (s.prev && s.prev.type == C.GRACE) {
			s.wl = wlnote - 4.5
		} else {
			s.wl = wlw
		}
		return
	case C.SPACE:
		xx = s.width / 2;
		s.wr = xx
		if (s.a_gch)
			xx = gchord_width(s, xx, xx)
		if (s.a_dd)
			xx = deco_width(s, xx)
		s.wl = xx
		return
	case C.BAR:
		bar_type = s.bar_type
			switch (bar_type) {
			case "|":
				w = 5		// 3 + 2
				break
			case "[":		// repeat number on secondary staff
				w = 0
				break
			default:
				w = 2 + 2.8 * bar_type.length
				for (i = 0; i < bar_type.length; i++) {
					switch (bar_type[i]) {
					case "[":
					case "]":
						w += 1
						// fall thru
					case ":":
						w += 2
						break
					}
				}
				if (bar_type[0] == ":"		// if "c3 :|"
				 && s.prev && s.prev.dots)
					w += 4
				break
			}
			s.wl = w
			if (s.next
			 && s.next.type != C.METER)
				s.wr = 7
			else
				s.wr = 5
//			s.notes[0].shhd = (w - 5) * -.5

		// special case for (mainly) "|| !invisible! |:"
		if (s.invis)
//fixme
//		 && s.prev && s.prev.bar_type)
			s.wl = s.wr = 2

			/* if preceeded by a grace note sequence, adjust */
			s2 = s.prev
			if (s2 && s2.type == C.GRACE)
				s.wl -= 6
			for ( ; s2; s2 = s2.prev) {
				if (w_tb[s2.type]) {
					if (s2.type == C.STBRK)
						s.wl -= 12
					break
				}
			}

		if (s.a_dd)
			s.wl = deco_width(s, s.wl)

		/* have room for the repeat numbers / chord indication */
		if (s.text && s.text.length < 4
		 && s.next && s.next.a_gch) {
			set_font("repeat");
			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
		}
		if (s.prev && s.prev.type == C.STBRK) {
			s.wl = 6
			s.wr = 13
			delete s.clef_small
			return
		}
		s.wl = s.clef_small ? 11 : 12
		s.wr = s.clef_small ? 8 : 13
		return
	case C.KEY:
		if (s.invis)
			break				// no width
		s.wl = 0
		esp = 3
			n1 = s.k_sf			/* new key sig */
			if (s.k_old_sf && (s.fmt.cancelkey || n1 == 0))
				n2 = s.k_old_sf	/* old key */
			else
				n2 = 0
			if (n1 * n2 >= 0) {		/* if no natural */
				if (n1 < 0)
					n1 = -n1
				if (n2 < 0)
					n2 = -n2
				if (n2 > n1)
					n1 = n2
			} else {
				n1 -= n2
				if (n1 < 0)
					n1 = -n1;
				esp += 3	/* see extra space in draw_keysig() */
			}
		if (s.k_bagpipe == 'p')		// K:Hp - add the g natural
			n1++
		if (s.k_a_acc) {
			n2 = s.k_a_acc.length
			if (s.exp)
				n1 = n2			// no key signature
			else
				n1 += n2
			if (n2)
				last_acc = s.k_a_acc[0].acc
			for (i = 1; i < n2; i++) {
				acc = s.k_a_acc[i]
				if (acc.pit > s.k_a_acc[i - 1].pit + 6
				 || acc.pit < s.k_a_acc[i - 1].pit - 6)
					n1--		// no clash
				else if (acc.acc != last_acc)
					esp += 3;
				last_acc = acc.acc
			}
		}
		if (!n1)
			break			// no width
		s.wr = 5.5 * n1 + esp
		if (s.prev && !s.prev.bar_type)
			s.wl += 2
		return
	case C.METER:
		s.x_meter = []
		if (!s.a_meter.length)
			break				// no width
		wlw = 0
		for (i = 0; i < s.a_meter.length; i++) {
			meter = s.a_meter[i]
			switch (meter.top[0]) {
			case 'C':
			case 'c':
			case 'o':
				s.x_meter[i] = wlw
				wlw += 14
				break
			default:
				w = 0
				if (!meter.bot
				 || meter.top.length > meter.bot.length)
					meter = meter.top
				else
					meter = meter.bot;
				for (m = 0; m < meter.length; m++) {
					switch (meter[m]) {
					case '(':
						wlw += 2
						w += 6
						break
					case ')':
						wlw -= 2
						w += 6
						break
					case '1':
						w += 6
						break
					case ' ':
						w += 2
						// fall thru
					case '.':
					case '|':
						break
					default:
						w += 12
						break
					}
				}
				s.x_meter[i] = wlw
				wlw += w
				break
			}
		}
		s.wl = 1
		s.wr = wlw + 4
		return
	case C.MREST:
		s.wl = 6;
		s.wr = 66
		return
	case C.GRACE:
		if (s.invis)
			break
		s.wl = set_graceoffs(s);
		s.wr = 0
		if (s.a_ly)
			ly_set(s)
		return
	case C.STBRK:
		s.wl = s.xmx
		s.wr = 8
		return
	case C.CUSTOS:
		s.wl = s.wr = 4
		return
	case C.TEMPO:		// no width, but build the tempo string
		tempo_build(s)
		break
	case C.BLOCK:				// no width
	case C.REMARK:
	case C.STAVES:
		break
	default:
		error(2, s, "set_width - Cannot set width for symbol $1", s.type)
		break
	}
	s.wl = s.wr = 0

	// move the invisible symbol to the next time sequence
	if (s.seqst && s.ts_next)
		s.ts_next.seqst = 0
}

// convert delta time to natural spacing
function time2space(s, len) {
    var i, l, space

	if (smallest_duration >= C.BLEN / 2) {
		if (smallest_duration >= C.BLEN)
			len /= 4
		else
			len /= 2
	} else if (!s.next && len >= C.BLEN) {
		len /= 2
	}
	if (len >= C.BLEN / 4) {
		if (len < C.BLEN / 2)
			i = 5
		else if (len < C.BLEN)
			i = 6
		else if (len < C.BLEN * 2)
			i = 7
		else if (len < C.BLEN * 4)
			i = 8
		else
			i = 9
	} else {
		if (len >= C.BLEN / 8)
			i = 4
		else if (len >= C.BLEN / 16)
			i = 3
		else if (len >= C.BLEN / 32)
			i = 2
		else if (len >= C.BLEN / 64)
			i = 1
		else
			i = 0
	}
	l = len - ((C.BLEN / 16 / 8) << i)
	space = cfmt.spatab[i]
	if (l) {
		if (l < 0) {
			space = cfmt.spatab[0] * len / (C.BLEN / 16 / 8)
		} else {
			if (i >= 9)
				i = 8
			space += (cfmt.spatab[i + 1] - cfmt.spatab[i]) *
					l / ((C.BLEN / 16 / 8) << i)
		}
	}
	return space
}

// set the natural space
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
///*fixme:do same thing at start of line*/
//		case C.NOTE:
//		case C.REST:
//			if (s.ts_prev.type == C.BAR) {
//				if (s.nflags < -2)
//					return cfmt.spatab[0]
//				return cfmt.spatab[2]
//			}
//			break
		}
		return 0
	}
	if (s.ts_prev.type == C.MREST)
//		return s.ts_prev.wr + 16
//				+ 3		// (bar wl=5 wr=8)
		return 71	// 66 (mrest.wl) + 5 (bar.wl)

	space = time2space(s, len)

	while (!s.dur) {
		switch (s.type) {
		case C.BAR:
			// (hack to have quite the same note widths between measures)
			if (!s.next)
				space *= .9
			return space * .9 - 3
		case C.CLEF:
			return space - s.wl - s.wr
		case C.BLOCK:			// no space
		case C.REMARK:
		case C.STAVES:
		case C.TEMPO:
			s = s.ts_next
			if (!s)
				return space
			continue
		case C.NOTE:
			break
		default:
			return space
		}
		break
	}

	/* reduce spacing within a beam */
	if (len <= C.BLEN / 4) {
		s2 = s
		while (s2) {
			if (!s2.beam_st) {
				space *= .9		// ex fnnp
				break
			}
			s2 = s2.ts_next
			if (!s2 || s2.seqst)
				break
		}
	}

	/* decrease spacing when stem down followed by stem up */
/*fixme:to be done later, after x computed in sym_glue*/
	if (s.nflags >= -1
	 && s.stem > 0) {
		stemdir = true

		for (s2 = s.ts_prev;
		     s2 && s2.time == ptime;
		     s2 = s2.ts_prev) {
			if (s2.type == C.NOTE
			 && (s2.nflags < -1 || s2.stem > 0)) {
				stemdir = false
				break
			}
		}
		if (stemdir) {
			for (s2 = s.ts_next;
			     s2 && s2.time == s.time;
			     s2 = s2.ts_next) {
				if (s2.type == C.NOTE
				 && (s2.nflags < -1 || s2.stem < 0)) {
					stemdir = false
					break
				}
			}
			if (stemdir)
				space *= .9
		}
	}
	return space
}

// set the spacing inside tuplets or L: factor
function set_sp_tup(s, s_et) {
    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) {
		s2 = s2.ts_next
		if (s2.seqst) {
			wsp += s2.space
			if (s2.bar_type)
				wsp += 10	// (fixme: not exact)
		}
		if (s2 == s_et)
			break
	}
	sp = (sp + wsp) / 2 / ttim	// mean spacing per time unit

	while (1) {
		s = s.ts_next
		if (s.seqst) {
			s.space = sp * (s.time - tim)
			tim = s.time
		}
		if (s == s_et)
			break
	}
}

// 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,
		time: s.time + (s.dur || 0),
		nhd: 0,
		notes: [{
			pit: s.notes ? s.notes[0].pit : 22
		}],
		seqst: true,
		invis: true,
		prev: s,
		fmt: s.fmt
	}
} // _bar()

// create an invisible bar for end of music lines
function add_end_bar(s) {
	if (s.type == C.KEY && !s.k_sf		// if an invisible key
	 && !s.k_old_sf && !s.k_a_acc
	 && s.prev && s.prev.bar_type) {	// before a bar
		unlksym(s)			// remove it
		return
	}

    var b = _bar(s),
	sn = s.ts_next		// start of next line

	b.wl = 0
	b.wr = 0
	b.ts_prev = s
	b.next = s.next
	b.ts_next = s.ts_next

	if (s.next)			// (must not be the end of the voice)
		s.next.prev = b
//	if (s.ts_next)
		s.ts_next.ts_prev = b
	s.next = s.ts_next = b
	b.shrink = sn.shrink
	sn.shrink = sn.wl + 10
	b.space = sn.space * .9 - 3
}

/* -- set the width and space of all symbols -- */
// this function is called once for the whole tune
// and once more for each new music line
function set_allsymwidth(first) {
    var	val, st, s_chs, stup, itup,
	s = tsfirst,
	s2 = s,
	xa = 0,
	xl = [],
	wr = [],
	maxx = xa,
	tim = s.time

	/* loop on all symbols */
	while (1) {
		itup = 0
		do {
			if ((s.a_gch || s.a_ly) && !s_chs)
				s_chs = s;
			self.set_width(s);
			st = s.st
			if (xl[st] == undefined)
				xl[st] = 0
			if (wr[st] == undefined)
				wr[st] = 0;
			if (s.prev && s.prev.st != st) {
				xl[st] = xl[s.prev.st]
				wr[st] = wr[s.prev.st]
			}
			val = xl[st] + wr[st] + s.wl
			if (val > maxx)
				maxx = val
			if (s.dur && s.dur != s.notes[0].dur)	// if in tuplet
				itup = 1
			s = s.ts_next
		} while (s && !s.seqst);

		// set the spaces of the time sequence
		s2.shrink = maxx - xa
		s2.space = s2.ts_prev ? set_space(s2, tim) : 0

		// adjust the spacing when after a spacer (y)
		if (s2.space == 0 && s2.ts_prev
		 && s2.ts_prev.type == C.SPACE && s2.ts_prev.seqst)
			s2.space = s2.ts_prev.space /= 2

		if (itup) {
			if (!first)
				break
			if (!stup)
				stup = s2
		} else if (stup && stup.v == s2.v) {
			set_sp_tup(stup, s2)
			stup = null
		}

		if (!s2.shrink) {
		    if (s2.type == C.CLEF
		     && !s2.ts_prev.bar_type) {
			delete s2.seqst;		/* no space */
			s2.time = tim
		    } else {
			s2.shrink = s2.wl		// cannot be null
			maxx += s2.wl
		    }
		}
		tim = s2.time
		if (!s)
			break

		// update the min left space per staff
		s = s2
		do {
			wr[s.st] = 0
			s = s.ts_next
		} while (!s.seqst)

		xa = maxx
		do {
			st = s2.st;
			xl[st] = xa
			if (s2.wr > wr[st])
				wr[st] = s2.wr
			s2 = s2.ts_next
		} while (!s2.seqst)
	}

	if (stup)
		set_sp_tup(stup, s2)

	// let the chord symbols at the same offset
	// and adjust the spacing due to the lyrics
	if (s_chs)
		set_w_chs(s_chs)
}

// insert a rest, this one replacing a sequence or a measure
function to_rest(so) {
    var	s = clone(so)

	s.prev.next = so.ts_prev = so.prev = s.ts_prev.ts_next = s
	s.next = s.ts_next = so
	so.seqst = false
	so.invis = so.play = true

	s.type = C.REST
// just keep nl and seqst
	delete s.in_tuplet
	delete s.tp
	delete s.a_dd
	delete s.a_gch
	delete s.sls
//fixme: what if chord / slur in notes / ... ?
/*fixme: should set many parameters for set_width*/
//	set_width(s)
	return s
}

/* -- set the repeat sequences / measures -- */
function set_repeat(s) {	// first note
    var	s2, s3, i, j, dur,
	n = s.repeat_n,
	k = s.repeat_k,
	st = s.st,
	v = s.v

	s.repeat_n = 0				// treated

	/* treat the sequence repeat */
	if (n < 0) {				/* number of notes / measures */
		n = -n;
		i = n				/* number of notes to repeat */
		for (s3 = s.prev; s3; s3 = s3.prev) {
			if (!s3.dur) {
				if (s3.type == C.BAR) {
					error(1, s3, "Bar in repeat sequence")
					return
				}
				continue
			}
			if (--i <= 0)
				break
		}
		if (!s3) {
			error(1, s, errs.not_enough_n)
			return
		}
		dur = s.time - s3.time;

		i = k * n		/* whole number of notes/rests to repeat */
		for (s2 = s; s2; s2 = s2.next) {
			if (!s2.dur) {
				if (s2.type == C.BAR) {
					error(1, s2, "Bar in repeat sequence")
					return
				}
				continue
			}
			if (--i <= 0)
				break
		}
		if (!s2
		 || !s2.next) {		/* should have some symbol */
			error(1, s, errs.not_enough_n)
			return
		}
		for (s2 = s.prev; s2 != s3; s2 = s2.prev) {
			if (s2.type == C.NOTE) {
				s2.beam_end = true
				break
			}
		}
		for (j = k; --j >= 0; ) {
			i = n			/* number of notes/rests */
			if (s.dur)
				i--;
			s2 = s.ts_next
			while (i > 0) {
				if (s2.st == st) {
					s2.invis = s2.play = true
					if (s2.seqst && s2.ts_next.seqst)
						s2.seqst = false
					if (s2.v == v
					 && s2.dur)
						i--
				}
				s2 = s2.ts_next
			}
			s = to_rest(s)
			s.dur = s.notes[0].dur = dur;
			s.rep_nb = -1;		// single repeat
			s.beam_st = true;
			self.set_width(s)
			s.head = C.SQUARE;
			for (s = s2; s; s = s.ts_next) {
				if (s.st == st
				 && s.v == v
				 && s.dur)
					break
			}
		}
		return
	}

	/* check the measure repeat */
	i = n				/* number of measures to repeat */
	for (s2 = s.prev.prev ; s2; s2 = s2.prev) {
		if (s2.type == C.BAR
		 || s2.time == tsfirst.time) {
			if (--i <= 0)
				break
		}
	}
	if (!s2) {
		error(1, s, errs.not_enough_m)
		return
	}

	dur = s.time - s2.time		/* repeat duration */

	if (n == 1)
		i = k			/* repeat number */
	else
		i = n			/* check only 2 measures */
	for (s2 = s; s2; s2 = s2.next) {
		if (s2.type == C.BAR) {
			if (--i <= 0)
				break
		}
	}
	if (!s2) {
		error(1, s, errs.not_enough_m)
		return
	}

	/* if many 'repeat 2 measures'
	 * insert a new %%repeat after the next bar */
	i = k				/* repeat number */
	if (n == 2 && i > 1) {
		s2 = s2.next
		if (!s2) {
			error(1, s, errs.not_enough_m)
			return
		}
		s2.repeat_n = n;
		s2.repeat_k = --i
	}

	/* replace */
	dur /= n
	if (n == 2) {			/* repeat 2 measures (once) */
		s3 = s
		for (s2 = s.ts_next; ; s2 = s2.ts_next) {
			if (s2.st != st)
				continue
			if (s2.type == C.BAR) {
				if (s2.v == v)
					break
				continue
			}
			s2.invis = s2.play = true
			if (s2.seqst && s2.ts_next.seqst)
				s2.seqst = false
		}
		s3 = to_rest(s3)
		s3.dur = s3.notes[0].dur = dur;
		s3.invis = true
		s2.bar_mrep = 2
		s3 = s2.next;
		for (s2 = s3.ts_next; ; s2 = s2.ts_next) {
			if (s2.st != st)
				continue
			if (s2.type == C.BAR) {
				if (s2.v == v)
					break
				continue
			}
			if (!s2.dur)
				continue
			s2.invis = s2.play = true
			if (s2.seqst && s2.ts_next.seqst)
				s2.seqst = false
		}
		s3 = to_rest(s3)
		s3.dur = s3.notes[0].dur = dur;
		s3.invis = true;
		self.set_width(s3)
		return
	}

	/* repeat 1 measure */
	s3 = s
	for (j = k; --j >= 0; ) {
		for (s2 = s3.ts_next; ; s2 = s2.ts_next) {
			if (s2.st != st)
				continue
			if (s2.type == C.BAR) {
				if (s2.v == v)
					break
				continue
			}
			if (!s2.dur)
				continue
			s2.invis = s2.play = true
			if (s2.seqst && s2.ts_next.seqst)
				s2.seqst = false
		}
		s3 = to_rest(s3)

		s3.dur = s3.notes[0].dur = dur;
		s3.beam_st = true
		if (k == 1) {
			s3.rep_nb = 1
			break
		}
		s3.rep_nb = k - j + 1;	// number to print above the repeat rest
		s3 = s2.next
	}
}

/* add a custos before the symbol of the next line */
function custos_add(s) {
	var	p_voice, new_s, i,
		s2 = s

	while (1) {
		if (s2.type == C.NOTE)
			break
		s2 = s2.next
		if (!s2)
			return
	}

	p_voice = s.p_v;
	p_voice.last_sym = s.prev;
//	if (!p_voice.last_sym)
//		p_voice.sym = null;
	p_voice.time = s.time;
	new_s = sym_add(p_voice, C.CUSTOS);
	new_s.next = s;
	s.prev = new_s;
	new_s.wl = 0			// (needed here for lktsym)
	new_s.wr = 4
	lktsym(new_s, s);

	new_s.shrink = s.shrink
	if (new_s.shrink < 8 + 4)
		new_s.shrink = 8 + 4;
	new_s.space = s2.space;

	new_s.head = C.FULL
	new_s.stem = s2.stem
	new_s.nhd = s2.nhd;
	new_s.notes = []
	for (i = 0; i < s2.notes.length; i++) {
		new_s.notes[i] = {
			pit: s2.notes[i].pit,
			shhd: 0,
			dur: C.BLEN / 4
		}
	}
	new_s.stemless = true
}

/* -- define the beginning of a new music line -- */
function set_nl(s) {			// s = start of line
    var	p_voice, done, tim, ptyp

	// divide the left repeat (|:) or variant bars (|1)
	// the new bars go in the next line
	function bardiv(so) {		// start of next line
	    var s, s1, s2, t1, t2, i

	    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
			// x|: -> || [|:
			t1 = '||'
			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()

		// change or add a bar for the voice in the previous line
		function eol_bar(s,		// bar |:
				 so,		// start of new line
				 sst) {		// first bar (for seqst)
		    var	s1, s2, s3

			// check if a bar in the previous line
			for (s1 = so.ts_prev ; s1.time == s.time; s1 = s1.ts_prev) {
				if (s1.v != s.v)
					continue
				if (s1.bar_type) {
					if (s1.bar_type != '|')
						return	// don't change
					s2 = s1		// last symbol in previous line
					break
				}
				if (!s3)
					s3 = s1.next	// possible anchor for the new bar
			}
			if (!s2) {			// if no symbol in previous line
				s2 = clone(s)
				if (!s3)
					s3 = s
				s2.next = s3
				s2.prev = s3.prev
				if (s2.prev)
					s2.prev.next = s2
				s3.prev = s2
				s2.ts_prev = so.ts_prev	// time linkage
				s2.ts_prev.ts_next = s2
				s2.ts_next = so
				so.ts_prev = s2
				if (s == sst)		// if first inserted bar
					s2.seqst = 1 //true
				if (s2.seqst) {
					for (s = s2.ts_next; !s.seqst; s = s.ts_next)
						;
					s2.shrink = s.shrink
					s.shrink = s2.wr + s.wl
					s2.space = s.space
					s.space = 0
				}
				delete s2.part
			}
			s2.bar_type = "||"
		} // eol_bar()

		// check if there is a left repeat bar at start of the new line
		s = so				// start of new music line
		while (s && s.time == so.time) {
			if (s.bar_type && s.bar_type.slice(-1) == ':') {
				s2 = s
				break
			}
			s = s.ts_next
		}
		if (s2) {
			s = s2
			while (1) {		// loop on all voices
				eol_bar(s2, so, s)
				s2 = s2.ts_next
				if (!s2 || s2.seqst)
					break
			}
			return so
		}

		s = so
		while (s.ts_prev
		 && s.ts_prev.time == so.time) {
			s = s.ts_prev
			if (s.bar_type)
				s1 = s		// first previous bar
			else if (!s1 && s.type == C.GRACE && s.seqst)
				so = s		// if grace note after a bar
						// move the start of line
		}
		if (!s1
		 || !s1.bar_type
		 || (s1.bar_type.slice(-1) != ':'
		  && !s1.text))
			return so

		// search the new start of the next line
		for (so = s1; so.time == s1.time; so = so.ts_prev) {
			switch (so.ts_prev.type) {
			case C.KEY:
			case C.METER:
//			case C.PART:
			case C.TEMPO:
			case C.STAVES:
			case C.STBRK:
				continue
			}
			break
		}

		// put the new bar before the end of music line
		s = s1				// keep first bar
		while (1) {
			new_type(s1)
			s2 = clone(s1)
			s2.bar_type = t1
			s1.bar_type = t2
			s2.ts_prev = so.ts_prev
			s2.ts_prev.ts_next = s2
			s2.ts_next = so
			so.ts_prev = s2
			if (s1 == s)
				s2.seqst = 1 //true
			s2.next = s1
			if (s2.prev)
				s2.prev.next = s2
			s1.prev = s2
			if (s1.rbstop)
				s2.rbstop = s1.rbstop
			if (s1.text) {
				s1.invis = 1 //true
				delete s1.xsh
				delete s2.text
				delete s2.rbstart
			}
			delete s2.part
			delete s1.a_dd
			delete s1.a_gch
			do {
				s1 = s1.ts_next
			} while (!s1.seqst && !s1.bar_type)
			if (s1.seqst)
				break
		}
		return so
	} // bardiv()

	// set the start of line marker
	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()

	// put the warning symbols
	// the new symbols go in the previous line
	function do_warn(s) {		// start of next line
	    var s1, s2, s3, s4, w

		// advance in the next line
		for (s2 = s; s2 && s2.time == s.time; s2 = s2.ts_next) {
			switch (s2.type) {
			case C.KEY:
				if (!s2.fmt.keywarn
				 || s2.invis)
					continue
				for (s1 = s.ts_prev; s1 ;s1 = s1.ts_prev) {
					if (s1.type != C.METER)
						break
				}
				// fall thru
			case C.METER:
				if (s2.type == C.METER) {
					if (!s.fmt.timewarn)
						continue
					s1 = s.ts_prev
				}
				// fall thru
			case C.CLEF:
				if (!s2.prev)		// start of voice
					continue
				if (s2.type == C.CLEF) {
					if (s2.clef_none) // if 'K: clef=none' after bar
						break
					for (s1 = s.ts_prev; s1; s1 = s1.ts_prev) {
						switch (s1.type) {
						case C.BAR:
							if (s1.bar_type[0] == ':')
								break
							// fall thru
						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

				s1 = s3
				while (1) {
					s1 = s1.ts_next
					if (s1.v == s2.v)
						break
				}
				lkvsym(s3, s1)		// voice link

				// care with spacing
				if (s3.seqst) {
					self.set_width(s3)
					s3.shrink = s3.wl
					s4 = s3.ts_prev
					w = 0
					while (1) {
						if (s4.wr > w)
							w = s4.wr
						if (s4.seqst)
							break
						s4 = s4.ts_prev
					}
					s3.shrink += w
					s3.space = 0
					s4 = s3
					while (1) {
						if (s4.ts_next.seqst)
							break
						s4 = s4.ts_next
					}
					w = 0
					while (1) {
						if (s4.wl > w)
							w = s4.wl
						s4 = s4.ts_next
						if (s4.seqst)
							break
					}
					s4.shrink = s3.wr + w
				}
				delete s3.part
				continue
			}
			if (w_tb[s2.type])
				break		// symbol with a width
		}
	} // do_warn()

	// divide the left repeat and variant bars
	s = bardiv(s)

	// add the warning symbols at the end of the previous line
	do_warn(s)

	/* if normal symbol, cut here */
	if (s.ts_prev.type != C.STAVES) {
		set_eol(s)
		return s
	}

	/* go back to handle the staff breaks at end of line */
	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.type == ptyp)
			continue
		ptyp = s.type
		if (done < 0)
			break
		switch (s.type) {
		case C.STAVES:
			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]
				 && s.ts_next.type != C.CLEF)
					break
				s = s.ts_next
			}
			if (!s.ts_next || s.ts_next.type != C.BAR)
				continue
			s = s.ts_next
			// fall thru
		case C.BAR:
			if (done)
				break
			done = 1;
			continue
		case C.STBRK:
			if (!s.stbrk_forced)
				unlksym(s)	/* remove */
			else
				done = -1	// keep the next symbols on the next line
			continue
		case C.CLEF:
			if (done)
				break
			continue
		default:
			if (!done || (s.prev && s.prev.type == C.GRACE))
				continue
			break
		}
		break
	}
	set_eol(s)
	return s
}

/* get the width of the starting clef and key signature */
// return
//	r[0] = width of clef and key signature
//	r[1] = width of the meter
function get_ck_width() {
    var	r0, r1,
	p_voice = voice_tb[0]

	self.set_width(p_voice.clef);
	self.set_width(p_voice.ckey);
	self.set_width(p_voice.meter)
	return [p_voice.clef.wl + p_voice.clef.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 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, next) {
    var	shrink, space,
	w = 0,
	wmx = 0,
	sp_fac = (1 - s.fmt.maxshrink)

	while (s != next) {
		if (s.seqst) {
			shrink = s.shrink
			wmx += shrink
			if ((space = s.space) < shrink)
				w += shrink
			else
				w += shrink * s.fmt.maxshrink
					+ space * sp_fac
			s.x = w
		}
		s = s.ts_next
	}
	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 */
			next,		/* symbol of the next line / null */
			lwidth,		/* w - (clef & key sig) */
			indent) {	/* for start of tune */
    var	first, s2, s3, s4, s5, x, xmin, xmid, xmax, wwidth, shrink, space,
	nlines,
	last = next ? next.ts_prev : null,
	ws = get_width(s, next)		// 2 widths: nice and shrunk

	// split a big lyric word on two music lines
	function ly_split(s, wmax) {
	    var	i, wh,
		s2 = clone(s),
		p = s.a_ly[0].t,		// lyric word
		w = 0,
		j = 0

		gene.deffont = s.a_ly[0].font
		while (1) {
			i = p.indexOf(' ', j) + 1
			if (i <= 0)
				break
			wh = strwh(p.slice(j, i))
			w += wh[0]
			if (w > wmax)
				break
			j = i
		}
		s.a_ly[0].t = new String(p.slice(0, j - 1))
		s2.a_ly = clone(s.a_ly)
		s2.a_ly[0] = clone(s.a_ly[0])
		s2.a_ly[0].t = new String(p.slice(j))
		if (abc2svg.el) {
			strwh(s.a_ly[0].t)
			strwh(s2.a_ly[0].t)
		} else {
			s.a_ly[0].t.wh = strwh(s.a_ly[0].t)
			s2.a_ly[0].t.wh = strwh(s2.a_ly[0].t)
		}
		w = s.a_ly[0].t.wh[0]		// new length of the words
		s.wr = wmax
		s2.wr -= w

		lkvsym(s2, s2.next)		// voice linkage

		s2.time += .1
//		s2.dur -= .1
		s2.nl = 0 //false
		while (!s.seqst)
			s = s.ts_prev
		s2.x = s.x + w
		s2.shrink = w - s2.wl
		s = s2.ts_next
		while (s) {
			if (s.seqst) {
				s.wl -= w
				s.shrink -= w
				break
			}
			s = s.ts_next
		}
		lktsym(s2, s2.ts_next)		// time linkage
	} // ly_split()

	// -- set_lines --

	// take care of big key signatures at end of line
	if (s.fmt.keywarn && next
	 && next.type == C.KEY && !last.dur) {
		ws[0] += next.wr
		ws[1] += next.wr
	}

	// check if the symbols can enter in one line (with max shrink)
	if (ws[1] + indent < lwidth) {
		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 (next)
				next = set_nl(next)
			return next || last
		}

		s2 = first = s;
		xmin = s.x
		xmax = xmin + lwidth;
		xmid = xmin + wwidth / nlines;
		xmin += wwidth / nlines * s.fmt.breaklimit;
		for (s = s.ts_next; s != next ; s = s.ts_next) {
			if (!s.x)
				continue
			if (s.type == C.BAR)
				s2 = s
			if (s.x >= xmin)
				break
		}
		s4 = s			// keep first symbol with x greater than xmin
//fixme: can this occur?
		if (s == next) {
			if (s)
				s = set_nl(s)
			return s
		}

		// if the width of the symbol is greater than
		//			 the remaining width in the staff
		// and if there are lyrics, split these lyrics on 2 staves
	    if (s.x > xmax
	     && s.prev.a_ly) {
		s3 = s = s.prev
		while (!s3.seqst)
			s3 = s3.ts_prev
		ly_split(s, xmax - s3.x)
	    } else {

		/* try to cut on a measure bar */
		s3 = null
		for ( ; s != next; s = s.ts_next) {
			x = s.x
			if (!x)
				continue
			if (x > xmax)
				break
			if (s.type != C.BAR)
				continue

			// cut on the bar closest to the middle
			if (x < xmid) {
				s3 = s		// closest bar before middle
				continue
			}
			if (!s3 || x - xmid < xmid - s3.x)
				s3 = s		// closest bar after middle
			break
		}

		// no bar, try to avoid to cut a beam or a tuplet */
		if (!s3) {
			s = s4			// restart after xmin

		    var	beam = 0,
			bar_time = s2.time

			xmax -= 8; // (left width of the inserted bar in set_allsymwidth)
			s5 = s
			for ( ; s != next; s = s.ts_next) {
				if (s.seqst) {
					x = s.x
					if (x + s.wr >= xmax)
						break
					if (!beam && !s.in_tuplet
					 && (xmid - s5.x > x - xmid
					  || (s.time - bar_time)
							% (C.BLEN / 4) == 0))
						s3 = s
				}
				if (s.beam_st)
					beam |= 1 << s.v
				if (s.beam_end)
					beam &= ~(1 << s.v)
				s5 = s		// start of new time sequence
			}
			if (s3) {
				do {		// cut on the previous sequence
					s3 = s3.ts_prev
				} while (!s3.seqst)
			}
		}

		// cut anyhere
		if (!s3) {
			s3 = s = s4
			for ( ; s != next; s = s.ts_next) {
				x = s.x
				if (!x)
					continue
				if (x + s.wr >= xmax)
					break
				if (s3 && x >= xmid) {
					if (xmid - s3.x > x - xmid)
						s3 = s
					break
				}
				s3 = s
			}
		}
		s = s3
	    } // ly_split call
		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 != next; s = s.ts_next) {
				if (!s.x)
					continue
				if (--nlines <= 0)
					break
			}
		}
		s = set_nl(s)
		if (!s
		 || (next && s.time >= next.time))
			break
		wwidth -= s.x - first.x;
		indent = 0
	}
	return s
}

/* -- cut the tune into music lines -- */
function cut_tune(lwidth, lsh) {
    var	s2, i, mc,
	pg_sav = {			// save the page parameters
		leftmargin: cfmt.leftmargin,
		rightmargin: cfmt.rightmargin,
		pagewidth: cfmt.pagewidth,
		scale: cfmt.scale
	},
	indent = lsh[1] - lsh[2],	// extra width of the first line
	ckw = get_ck_width(),		// width of the starting symbols
	s = tsfirst

	lwidth -= lsh[2]		// width of the lines
	if (cfmt.indent && cfmt.indent > lsh[1])
		indent += cfmt.indent

	// adjust the line width according to the starting symbols
	lwidth -= ckw[0]
	indent += ckw[1]

	if (cfmt.custos && voice_tb.length == 1)
		lwidth -= 12

	/* if asked, count the measures and set the EOLNs */
	i = s.fmt.barsperstaff
	if (i) {
		for (s2 = s; s2; s2 = s2.ts_next) {
			if (s2.type != C.BAR
			 || !s2.bar_num
			 || --i > 0)
				continue
			while (s2.ts_next && s2.ts_next.type == C.BAR)
				s2 = s2.ts_next
			if (s2.ts_next)
				s2.ts_next.soln = true
			i = s.fmt.barsperstaff
		}
	}

	/* cut at explicit end of line, checking the line width */
	s2 = s
	for ( ; s; s = s.ts_next) {
		if (s.type == C.BLOCK) {
			switch (s.subtype) {
			case "leftmargin":
			case "rightmargin":
			case "pagescale":
			case "pagewidth":
			case "scale":
			case "staffwidth":
				if (!s.soln)
					self.set_format(s.subtype, s.param)
				break
			case "mc_start":
				mc = {
					lm: cfmt.leftmargin,
					rm: cfmt.rightmargin
				}
				break
			case "mc_new":
			case "mc_end":
				if (!mc)
					break
				cfmt.leftmargin = mc.lm
				cfmt.rightmargin = mc.rm
				img.chg = 1 //true
				break
			}
		}
		if (!s.ts_next) {
			s = null
		} else if (!s.soln) {
			continue
		} else {
			s.soln = false
//fixme what if new line wanted?
			if (s.time == s2.time)
				continue	// empty music line!
			while (!s.seqst)
				s = s.ts_prev
		}
		set_page()
		lwidth = get_lwidth() - lsh[1] - ckw[0]
		s2 = set_lines(s2, s, lwidth, indent)
		if (!s2)
			break

		s = s2.type == C.BLOCK
			? s2.ts_prev		// don't miss a parameter
			: s
		indent = 0
	}

	// restore the page parameters at start of line
	cfmt.leftmargin = pg_sav.leftmargin
	cfmt.rightmargin = pg_sav.rightmargin
	cfmt.pagewidth = pg_sav.pagewidth
	cfmt.scale = pg_sav.scale
	img.chg = 1
	set_page()
}

/* -- set the y values of some symbols -- */
function set_yval(s) {
//fixme: staff_tb is not yet defined
//	var top = staff_tb[s.st].topbar
//	var bot = staff_tb[s.st].botbar
	switch (s.type) {
	case C.CLEF:
		s.y = (s.clef_line - 1) * 6
		if (s.second
		 || s.invis) {
//			s.ymx = s.ymn = (top + bot) / 2
			s.ymx = s.ymn = 12
			break
		}
		switch (s.clef_type) {
		default:			/* treble / perc */
			s.ymx = s.y + 25
			s.ymn = s.y - 14
			break
		case "c":
			s.ymx = s.y + 12
			s.ymn = s.y - 11
			break
		case "b":
			s.ymx = s.y + 4
			s.ymn = s.y - 11//12
			break
		}
		if (s.clef_small) {
			s.ymx -= 3
			s.ymn += 2
		}
		if (s.ymx < 24)
			s.ymx = 24
//		if (s.ymn > -1)
//			s.ymn = -1
		if (s.ymn > 0)
			s.ymn = 0
//		s.y += s.clef_line * 6
//		if (s.y > 0)
//			s.ymx += s.y
//		else if (s.y < 0)
//			s.ymn += s.y
		if (s.clef_octave) {
			if (s.clef_octave > 0)
				s.ymx += 4
			else
				s.ymn -= 4
		}
		break
	case C.KEY:
		if (s.k_sf > 2)
			s.ymx = 24 + 10
		else if (s.k_sf > 0)
			s.ymx = 24 + 6
		else
			s.ymx = 24 + 2;
		s.ymn = -2
		break
	default:
//		s.ymx = top;
		s.ymx = 24;
		s.ymn = 0
		break
	}
}

// set the pitch of the notes under an ottava sequence
function set_ottava() {
    var	s, s1, st, o, d,
	m = nstaff + 1,
	staff_d = new Int8Array(m)

	// update the pitches of a symbol
	function sym_ott(s, d) {
	    var	g, m, note

		switch (s.type) {
		case C.REST:
			if (voice_tb.length == 1)
				break
		case C.NOTE:
			if (!s.p_v.ckey.k_drum) {
				for (m = s.nhd; m >= 0; m--) {
					note = s.notes[m];
					if (!note.opit)
						note.opit = note.pit;
					note.pit += d
				}
			}
			break
		case C.GRACE:
			for (g = s.extra; g; g = g.next) {
				if (!s.p_v.ckey.k_drum) {
					for (m = 0; m <= g.nhd; m++) {
						note = g.notes[m]
						if (!note.opit)
							note.opit = note.pit
						note.pit += d
					}
				}
			}
			break
		}
	} // sym_ott()

	// remove the ottava decorations of a symbol
	function deco_rm(s) {
		for (var i = s.a_dd.length; --i >= 0;) {
			if (s.a_dd[i].name.match(/1?[85][vm][ab]/))
				s.a_dd.splice(i, 1)
		}
	} // deco_rm()

	for (s = tsfirst; s; s = s.ts_next) {
		st = s.st
		o = s.ottava
		if (o) {				// some ottava start or stop
			if (o[0]) {
				if (staff_d[st] && !o[1]) {
					sym_ott(s, staff_d[st])
					deco_rm(s)
					continue	// same ottava
				}
			} else if (!staff_d[st]) {
				deco_rm(s)
				continue		// already no ottava
			}
			s1 = s
			while (s1 && !s1.seqst)
				s1 = s1.ts_prev
			if (s1) {			// update the previous symbols
				while (s1 != s) {
					if (s1.st == st) {
						if (o[1])
							sym_ott(s1, -staff_d[st])
						if (o[0])
							sym_ott(s1, -o[0] * 7)
					}
					s1 = s1.ts_next
				}
			}
			if (o[0]) {			// ottava start
				staff_d[st] = -o[0] * 7
			} else {
				staff_d[st] = 0
			}
		}
		if (staff_d[st])
			sym_ott(s, staff_d[st])
	}
}

// expand the multi-rests as needed
function mrest_expand() {
    var	s, s2

	// expand a multi-rest into a set of rest + bar
	function mexp(s) {
	    var	bar, s3, s4, tim, nbar,
		nb = s.nmes,
		dur = s.dur / nb,
		s2 = s.next

		// get the bar (there may be some other symbols before the bar)
		while (s2 && !s2.bar_type)
			s2 = s2.next
		if (!s2)
			return error(1, s, "Lack of bar after multi-measure rest")
		bar = s2
		while (!s2.bar_num)		// get the bar number
			s2 = s2.ts_prev
		nbar = s2.bar_num - s.nmes

		// change the multi-rest into a single rest
		s.type = C.REST
		s.notes[0].dur = s.dur = s.dur_orig = dur
		s.nflags = -2
		s.head = C.FULL
		s.fmr = 1			// full measure rest

		/* add the bar(s) and rest(s) */
		tim = s.time + dur
		s3 = s
		while (--nb > 0) {

			// add the bar
			s2 = clone(bar)
			delete s2.soln
			delete s2.a_gch
			delete s2.a_dd
			delete s2.text
			delete s2.rbstart
			delete s2.rbstop
			lkvsym(s2, s.next)	// before symbol at end of rests

			s2.time = tim
			while (s3.time < tim)
				s3 = s3.ts_next	// bar at end of measure
			if (s3.time == tim)
				while (!s3.bar_type
				    && s3.ts_next && s3.ts_next.time == tim)
					s3 = s3.ts_next
			while (s3 && s3.v < s.v && s3.type == C.BAR)
				s3 = s3.ts_next	// keep in order
			if (s3) {
				if (s3.bar_type)
					s3.seqst = 0 //false
				lktsym(s2, s3)
				if (s3.type == C.BAR)
					delete s3.bar_num
			} else {
				s3 = s
				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
			delete s4.a_gch
			delete s4.part
			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) {
				if (s3.dur)
					s3.seqst = 0 //false
				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, time, s2, s3,
	max = 14,				// "A,"
	min = 18				// "E"

	/* get the max and min pitches in the sequence */
	for (s = s_start; s; s = s.ts_next) {
		if (s.type == C.STAVES && s != s_start)
			break
		if (s.st != st)
			continue
		if (s.type != C.NOTE) {
			if (s.type == C.CLEF) {
				if (s.clef_type != 'a')
					break
				unlksym(s)
			}
			continue
		}
		if (s.notes[0].pit < min)
			min = s.notes[0].pit
		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,' */
		return 't'
	if (max <= 13					/* lower than 'G,' */
	 || (max <= 19 && clef_type_start != 't'))	/* or 'F' */
		return 'b'

	/* set clef changes */
	if (clef_type_start == 'a') {
		if ((max + min) / 2 >= 16)
			clef_type_start = 't'
		else
			clef_type_start = 'b'
	}
	var	clef_type = clef_type_start,
		s_last = s,
		s_last_chg = null
	for (s = s_start; s != s_last; s = s.ts_next) {
		if (s.type == C.STAVES && s != s_start)
			break
		if (s.st != st || s.type != C.NOTE)
			continue

		/* check if a clef change may occur */
		time = s.time
		if (clef_type == 't') {
			if (s.notes[0].pit > 12		/* F, */
			 || s.notes[s.nhd].pit > 20) {	/* G */
				if (s.notes[0].pit > 20
				 || s.notes[s.nhd].pit > 20)
					s_last_chg = s
				continue
			}
			s2 = s.ts_prev
			if (s2
			 && s2.time == time
			 && s2.st == st
			 && s2.type == C.NOTE
			 && s2.notes[0].pit >= 19)	/* F */
				continue
			s2 = s.ts_next
			if (s2
			 && s2.st == st
			 && s2.time == time
			 && s2.type == C.NOTE
			 && s2.notes[0].pit >= 19)	/* F */
				continue
		} else {
			if (s.notes[0].pit <= 12	/* F, */
			 || s.notes[s.nhd].pit < 20) {	/* G */
				if (s.notes[s.nhd].pit <= 12
				 || s.notes[0].pit <= 12)
					s_last_chg = s
				continue
			}
			s2 = s.ts_prev
			if (s2
			 && s2.time == time
			 && s2.st == st
			 && s2.type == C.NOTE
			 && s2.notes[0].pit <= 13)	/* G, */
				continue
			s2 = s.ts_next
			if (s2
			 && s2.st == st
			 && s2.time == time
			 && s2.type == C.NOTE
			 && s2.notes[0].pit <= 13)	/* G, */
				continue
		}

		/* if first change, change the starting clef */
		if (!s_last_chg) {
			clef_type = clef_type_start =
					clef_type == 't' ? 'b' : 't';
			s_last_chg = s
			continue
		}

		/* go backwards and search where to insert a clef change */
		s3 = s
		for (s2 = s.ts_prev; s2 != s_last_chg; s2 = s2.ts_prev) {
			if (s2.st != st)
				continue
			if (s2.type == C.BAR) {
				s3 = s2.bar_type[0] != ':' ? s2 : s2.next
				break
			}
			if (s2.type != C.NOTE)
				continue

			/* have a 2nd choice on beam start */
			if (s2.beam_st
			 && !s2.p_v.second)
				s3 = s2
		}

		/* no change possible if no insert point */
		if (s3.time == s_last_chg.time) {
			s_last_chg = s
			continue
		}
		s_last_chg = s;

		/* insert a clef change */
		clef_type = clef_type == 't' ? 'b' : 't';
		s2 = insert_clef(s3, clef_type, clef_type == "t" ? 2 : 4);
		s2.clef_auto = true
//		s3.prev.st = st
	}
	return clef_type_start
}

/* set the clefs */
/* this function is called once at start of tune generation */
/*
 * global variables:
 *	- staff_tb[st].clef = clefs at start of line (here, start of tune)
 *				(created here, updated on clef draw)
 *	- voice_tb[v].clef = clefs at end of generation
 *				(created on voice creation, updated here)
 */
function set_clefs() {
    var	s, s2, st, v, p_voice, g, new_type, new_line, p_staff, pit,
	staff_clef = new Array(nstaff + 1),	// st -> { clef, autoclef }
	sy = cur_sy,
	mid = []

	// create the staff table
	staff_tb = new Array(nstaff + 1)
	for (st = 0; st <= nstaff; st++) {
		staff_clef[st] = {
			autoclef: true
		}
		staff_tb[st] = {
			output: "",
			sc_out: ""
		}
	}

	for (st = 0; st <= sy.nstaff; st++)
		mid[st] = (sy.staves[st].stafflines.length - 1) * 3

	for (s = tsfirst; s; s = s.ts_next) {
		if (s.repeat_n)
			set_repeat(s)

		switch (s.type) {
		case C.STAVES:
			sy = s.sy			// new system
			for (st = 0; st <= nstaff; st++)
				staff_clef[st].autoclef = true
			for (v = 0; v < voice_tb.length; v++) {
				if (!sy.voices[v])
					continue
				p_voice = voice_tb[v];
				st = sy.voices[v].st
				if (!sy.voices[v].second) {
					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)
						sy.staves[st].maxsep = sy.voices[v].maxsep
				}
				s2 = p_voice.clef
				if (!s2.clef_auto)
					staff_clef[st].autoclef = false
			}
			for (st = 0; st <= sy.nstaff; st++)
				mid[st] = (sy.staves[st].stafflines.length - 1) * 3
			for (v = 0; v < voice_tb.length; v++) {
				if (!sy.voices[v]
				 || sy.voices[v].second)	// main voices
					continue
				p_voice = voice_tb[v];
				st = sy.voices[v].st;
				s2 = p_voice.clef
				if (s2.clef_auto) {
//fixme: the staff may have other voices with explicit clefs...
//					if (!staff_clef[st].autoclef)
//						???
					new_type = set_auto_clef(st, s,
						staff_clef[st].clef ?
							staff_clef[st].clef.clef_type :
							'a');
					new_line = new_type == 't' ? 2 : 4
					set_yval(p_voice.clef)
				} else {
					new_type = s2.clef_type;
					new_line = s2.clef_line
				}
				if (!staff_clef[st].clef) {	// new staff
					if (s2.clef_auto) {
						if (s2.clef_type != 'a')
							p_voice.clef =
								clone(p_voice.clef);
						p_voice.clef.clef_type = new_type;
						p_voice.clef.clef_line = new_line
						set_yval(p_voice.clef)
					}
					staff_tb[st].clef =
						staff_clef[st].clef = p_voice.clef
					continue
				}
								// old staff
				if (new_type == staff_clef[st].clef.clef_type
				 && new_line == staff_clef[st].clef.clef_line)
					continue
				g = s.ts_prev
				while (g
				 && g.time == s.time
				 && (g.v != v || g.st != st))
					g = g.ts_prev
				if (!g || g.time != s.time) {
					g = s.ts_next
					while (g && (g.v != v || g.st != st))
						g = g.ts_next
					if (!g || g.time != s.time)
						g = s
				}
				if (g.type != C.CLEF) {
					g = insert_clef(g, new_type, new_line)
					if (s2.clef_auto)
						g.clef_auto = true
				}
				staff_clef[st].clef = p_voice.clef = g
			}
			continue
		default:
			s.mid = mid[s.st]
			continue
		case C.CLEF:
			break
		}

		if (s.clef_type == 'a') {
			s.clef_type = set_auto_clef(s.st,
						s.ts_next,
						staff_clef[s.st].clef.clef_type);
			s.clef_line = s.clef_type == 't' ? 2 : 4
			set_yval(s)
		}

		p_voice = s.p_v;
		p_voice.clef = s
		st = s.st
// may have been inserted on %%staves
//		if (s.clef_auto) {
//			unlksym(s)
//			continue
//		}

		if (staff_clef[st].clef) {
			if (s.clef_type == staff_clef[st].clef.clef_type
			 && s.clef_line == staff_clef[st].clef.clef_line) {
//				unlksym(s)
				continue
			}
		} else {

			// the voice moved to a new staff with a forced clef
			staff_tb[st].clef = s
		}
		staff_clef[st].clef = s
	}

	/* set a pitch to the symbols of voices with no note */
	sy = cur_sy
	for (v = 0; v < voice_tb.length; v++) {
		if (!sy.voices[v])
			continue
		s2 = voice_tb[v].sym
		if (!s2 || s2.notes[0].pit != 127)
			continue
		st = sy.voices[v].st
		switch (staff_tb[st].clef.clef_type) {
		default:
			pit = 22		/* 'B' */
			break
		case "c":
			pit = 16		/* 'C' */
			break
		case "b":
			pit = 10		/* 'D,' */
			break
		}
		for (s = s2; s; s = s.next)
			s.notes[0].pit = pit
	}
}

/* set the pitch of the notes according to the clefs
 * and set the vertical offset of the symbols */
/* this function is called at start of tune generation and
 * then, once per music line up to the old sequence */

var delta_tb = {
	t: 0 - 2 * 2,
	c: 6 - 3 * 2,
	b: 12 - 4 * 2,
	p: 0 - 3 * 2
}

/* upper and lower space needed by rests */
var rest_sp = [
	[18, 18],
	[12, 18],
	[12, 12],
	[10, 12],
	[10, 10],
	[10, 10],			/* crotchet */
	[8, 4],
	[9, 0],
	[9, 4],
	[6, 8]
]

// set the offsets of a rest
function roffs(s) {
	s.ymx = s.y + rest_sp[5 - s.nflags][0]
	s.ymn = s.y - rest_sp[5 - s.nflags][1]
} // roffs()

// (possible hook)
Abc.prototype.set_pitch = function(last_s) {
	var	s, s2, g, st, delta, pitch, note,
		dur = C.BLEN,
		m = nstaff + 1,
		staff_delta = new Int16Array(m * 2),	// delta clef
		sy = cur_sy

	// set the starting clefs of the staves
	for (st = 0; st <= nstaff; st++) {
		s = staff_tb[st].clef;
		staff_delta[st] = delta_tb[s.clef_type] + s.clef_line * 2
		if (s.clefpit)
			staff_delta[st] += s.clefpit
		if (cfmt.sound) {
			if (s.clef_octave && !s.clef_oct_transp)
				staff_delta[st] += s.clef_octave
		} else {
			if (s.clef_oct_transp)
				staff_delta[st] -= s.clef_octave
		}
	}

	for (s = tsfirst; s != last_s; s = s.ts_next) {
		st = s.st
		switch (s.type) {
		case C.CLEF:
			staff_delta[st] = delta_tb[s.clef_type] +
						s.clef_line * 2
			if (s.clefpit)
				staff_delta[st] += s.clefpit
			if (cfmt.sound) {
				if (s.clef_octave && !s.clef_oct_transp)
					staff_delta[st] += s.clef_octave
			} else {
				if (s.clef_oct_transp)
					staff_delta[st] -= s.clef_octave
			}
			set_yval(s)
			break
		case C.GRACE:
			for (g = s.extra; g; g = g.next) {
				delta = staff_delta[g.st]
				if (delta
				 && !s.p_v.ckey.k_drum) {
					for (m = 0; m <= g.nhd; m++) {
						note = g.notes[m];
						note.opit = note.pit
						note.pit += delta
					}
				}
				g.ymn = 3 * (g.notes[0].pit - 18) - 2;
				g.ymx = 3 * (g.notes[g.nhd].pit - 18) + 2
			}
			set_yval(s)
			break
		case C.KEY:
			s.k_y_clef = staff_delta[st] /* keep the y delta */
			/* fall thru */
		default:
			set_yval(s)
			break
		case C.MREST:
			if (s.invis)
				break
			s.y = 12;
			s.ymx = 24 + 15;
			s.ymn = -2
			break
		case C.REST:
			s.y = 12
			if (s.rep_nb > 1		// if measure repeat
			 || s.bar_mrep) {
				s.ymx = 38		// (24 + 14)
				s.ymn = 0
				break
			}
			roffs(s)
			// fall thru
		case C.NOTE:
			delta = staff_delta[st]
			if (delta
			 && !s.p_v.ckey.k_drum) {
				for (m = s.nhd; m >= 0; m--) {
					note = s.notes[m]
					note.opit = note.pit
					note.pit += delta
				}
			}
			if (s.dur < dur)
				dur = s.dur
			break
		}
	}
	if (!last_s)
		smallest_duration = dur
}

/* -- set the stem direction when multi-voices -- */
/* this function is called only once per tune */
// (possible hook)
Abc.prototype.set_stem_dir = function() {
	var	t, u, i, st, rvoice, v,
		v_st,			// voice -> staff 1 & 2
		st_v, vobj,		// staff -> (v, ymx, ymn)*
		v_st_tb,		// array of v_st
		st_v_tb = [],		// array of st_v
		s = tsfirst,
		sy = cur_sy,
		nst = sy.nstaff

	while (s) {
		for (st = 0; st <= nst; st++)
			st_v_tb[st] = []
		v_st_tb = []

		/* get the max/min offsets in the delta time */
/*fixme: the stem height is not calculated yet*/
		for (u = s; u; u = u.ts_next) {
			if (u.type == C.BAR)
				break;
			if (u.type == C.STAVES) {
				if (u != s)
					break
				sy = s.sy
				for (st = nst; st <= sy.nstaff; st++)
					st_v_tb[st] = []
				nst = sy.nstaff
				continue
			}
			if ((u.type != C.NOTE && u.type != C.REST)
			 || u.invis)
				continue
			st = u.st;
/*fixme:test*/
if (st > nst) {
	var msg = "*** fatal set_stem_dir(): bad staff number " + st +
			" max " + nst;
	error(2, null, msg);
	throw new Error(msg)
}
			v = u.v;
			v_st = v_st_tb[v]
			if (!v_st) {
				v_st = {
					st1: -1,
					st2: -1
				}
				v_st_tb[v] = v_st
			}
			if (v_st.st1 < 0) {
				v_st.st1 = st
			} else if (v_st.st1 != st) {
				if (st > v_st.st1) {
					if (st > v_st.st2)
						v_st.st2 = st
				} else {
					if (v_st.st1 > v_st.st2)
						v_st.st2 = v_st.st1;
					v_st.st1 = st
				}
			}
			st_v = st_v_tb[st];
			rvoice = sy.voices[v].range;
			for (i = st_v.length; --i >= 0; ) {
				vobj = st_v[i]
				if (vobj.v == rvoice)
					break
			}
			if (i < 0) {
				vobj = {
					v: rvoice,
					ymx: 0,
					ymn: 24
				}
				for (i = 0; i < st_v.length; i++) {
					if (rvoice < st_v[i].v) {
						st_v.splice(i, 0, vobj)
						break
					}
				}
				if (i == st_v.length)
					st_v.push(vobj)
			}

			if (u.type != C.NOTE)
				continue
			if (u.ymx > vobj.ymx)
				vobj.ymx = u.ymx
			if (u.ymn < vobj.ymn)
				vobj.ymn = u.ymn

			if (u.xstem) {
				if (u.ts_prev.st != st - 1
				 || u.ts_prev.type != C.NOTE) {
					error(1, s, "Bad !xstem!");
					u.xstem = false
/*fixme:nflags KO*/
				} else {
					u.ts_prev.multi = 1;
					u.multi = 1;
					u.stemless = true
				}
			}
		}

		for ( ; s != u; s = s.ts_next) {
			if (s.multi)
				continue
			switch (s.type) {
			default:
				continue
			case C.REST:
				// handle %%voicecombine 0
				if ((s.combine != undefined && s.combine < 0)
				 || !s.ts_next || s.ts_next.type != C.REST
				 || s.ts_next.st != s.st
				 || s.time != s.ts_next.time
				 || s.dur != s.ts_next.dur
				 || (s.a_dd && s.ts_next.a_dd)
				 || (s.a_gch && s.ts_next.a_gch)
				 || s.invis)
					break
				if (s.ts_next.a_dd)
					s.a_dd = s.ts_next.a_dd
				if (s.ts_next.a_gch)
					s.a_gch = s.ts_next.a_gch
				if (s.p_v.scale != 1
				 && s.ts_next.p_v.scale > s.p_v.scale)
				 	unlksym(s)
				else
					unlksym(s.ts_next)

				if ((!s.ts_prev.dur || s.ts_prev.time != s.time
				  || s.ts_prev.st != s.st)
				 && (s.ts_next.time != s.time
				  || s.ts_next.st != s.st))
					continue	// rest alone in the staff
				// fall thru
			case C.NOTE:
			case C.GRACE:
				break
			}

			st = s.st;
			v = s.v;
			v_st = v_st_tb[v];
			st_v = st_v_tb[st]
			if (v_st && v_st.st2 >= 0) {
				if (st == v_st.st1)
					s.multi = -1
				else if (st == v_st.st2)
					s.multi = 1
				continue
			}
			if (st_v.length <= 1) { /* voice alone on the staff */
//				if (s.multi)
//					continue
/*fixme:could be done in set_var()*/
				if (s.floating)
					s.multi = st == voice_tb[v].st ? -1 : 1
				continue
			}
			rvoice = sy.voices[v].range
			for (i = st_v.length; --i >= 0; ) {
				if (st_v[i].v == rvoice)
					break
			}
			if (i < 0)
				continue		/* voice ignored */
			if (i == st_v.length - 1) {
				s.multi = -1	/* last voice */
			} else {
				s.multi = 1	/* first voice(s) */

				/* if 3 voices, and vertical space enough,
				 * have stems down for the middle voice */
				if (i && i + 2 == st_v.length) {
					if (st_v[i].ymn - s.fmt.stemheight
							>= st_v[i + 1].ymx)
						s.multi = -1;

					/* special case for unison */
					t = s.ts_next
//fixme: pb with ../lacerda/evol-7.5.5.abc
					if (s.ts_prev
					 && s.ts_prev.time == s.time
					 && s.ts_prev.st == s.st
					 && s.notes[s.nhd].pit == s.ts_prev.notes[0].pit
					 && s.beam_st
					 && s.beam_end
					 && (!t
					  || t.st != s.st
					  || t.time != s.time))
						s.multi = -1
				}
			}
		}
		while (s && s.type == C.BAR)
			s = s.ts_next
	}
}

/* -- adjust the offset of the rests when many voices -- */
/* this function is called only once per tune */
function set_rest_offset() {
   var	s, s2, v, v_s, ymax, ymin, d,
		v_s_tb = [],
		sy = cur_sy

	// set a vertical offset on a line 
	function loffs(d) {
		return d > 0
			? Math.ceil(d / 6) * 6
			: -Math.ceil(-d / 6) * 6
	} // loffs()

	// shift a rest to the right
	function rshift() {
	    var	dx = s2.dots ? 15 : 10
		s2.notes[0].shhd = dx
		s2.xmx = dx
		d = (d + v_s.d) / 2
		d = loffs(d)
		s2.y += d
		s2.ymx += d
		s2.ymn += d
		v_s.d = 0
	} // rshift()

	// -- set_rest_off --
	for (s = tsfirst; s; s = s.ts_next) {
		if (s.invis)
			continue
		switch (s.type) {
		case C.STAVES:
			sy = s.sy
			// fall thru
		default:
			continue
		case C.REST:
			if (s.invis || !s.multi)
				continue
			v_s = v_s_tb[s.v]
			if (!v_s) {
				v_s_tb[s.v] = v_s = { d: 0}
			} else if (v_s.d) {
				s2 = v_s.s	// set the offsets of the previous rest
				d = loffs(v_s.d)
				s2.y += d
				s2.ymx += d
				s2.ymn += d
				v_s.d = 0
			}

			d = s.multi > 0 ? 0 : 24
				if (s.prev && s.prev.type == C.NOTE)
					d = (s.next && s.next.type == C.NOTE)
						? (s.prev.y + s.next.y) / 2
						: s.prev.y
				else if (s.next && s.next.type == C.NOTE)
					d = s.next.y
			else if (s.prev && s.prev.type == C.REST)
				d = s.prev.y
			if (s.multi > 0) {
				if (d >= 12)
					v_s.d = d - s.y
			} else {
				if (d <= 12)
					v_s.d = d - s.y
			}

			v_s.s = s
			v_s.st = s.st
			v_s.end_time = s.time + s.dur
			if (s.fmr)			// if full meeasure rest
				v_s.end_time -= s.p_v.wmeasure * .3
			if (s.seqst)
				continue
			s2 = s.ts_prev
			if (s2.st != s.st
			 || s2.invis)
				continue
			d = s2.ymn
			if (v_s_tb[s2.v] && v_s_tb[s2.v].d
			 && v_s_tb[s2.v] >= s.time)
				d += v_s_tb[s2.v].d
			if (s.ymx <= d)
				continue
			if (s2.type == C.NOTE) {
				v_s.d = d - s.ymx
				break
			}
			if (s2.type == C.REST
			 && s2.y < 18
			 && s.y >= 6)
				v_s.d = (d - s.ymx) / 2
			break
		case C.NOTE:
			if (s.invis || !s.multi)
				continue
			break
		}

		// check if any clash with a rest
		for (v = 0; v < v_s_tb.length; v++) {
			v_s = v_s_tb[v]
			if (!v_s
			 || v_s.st != s.st
			 || v == s.v
			 || v_s.end_time <= s.time)
				continue
			s2 = v_s.s				// rest
			if (sy.voices[v].range > sy.voices[s.v].range) {
				if (s2.ymx + v_s.d <= s.ymn)
					continue
				d = s.ymn - s2.ymx		// rest must go down
//				if (s2.time < s.time) {
					if (s.type == C.REST) {
						if (!v_s_tb[s.v])
							v_s_tb[s.v] = {d: 0}
						if (v_s_tb[s.v].d < 6)
							v_s_tb[s.v].d = 6
						d = -6
					} else {
						d /= 2
						if (s2.fmr)
							d -= 6
					}
//				}
				if (v_s.d) {
					if (v_s.d > 0) {	// if it was go up
						rshift()	// shift the rest
						continue
					}
					if (d >= v_s.d)
						continue
				}
			} else {
				if (s2.ymn + v_s.d >= s.ymx)
					continue
				d = s.ymx - s2.ymn		// rest must go up
				if (s.type == C.REST		// if rest
				 && s2 == s.ts_prev		// just under a rest
				 && s.y == s2.y) {		// at a same offset
					if (!v_s_tb[s.v])
						v_s_tb[s.v] = {d: 0}
					if (v_s_tb[s.v].d > -6)
						v_s_tb[s.v].d = -6
					d = 6
				} else if (s2.time < s.time) {
					d = s.ymx - s2.y
				}
				if (v_s.d) {
					if (v_s.d < 0) {	// if it was go down
						rshift()	// shift the rest
						continue
					}
					if (d <= v_s.d)
						continue
				}
			}
			v_s.d = d
		}
	}

	// update the offsets of the last rests
	for (v = 0; v < v_s_tb.length; v++) {
		v_s = v_s_tb[v]
		if (v_s && v_s.d) {
			s2 = v_s.s
			d = loffs(v_s.d)
			s2.y += d
			s2.ymx += d
			s2.ymn += d
		}
	}
}

/* -- create a starting symbol -- */
// last_s = symbol at same time
function new_sym(s, p_v, last_s) {
	s.p_v = p_v
	s.v = p_v.v
	s.st = p_v.st
	s.time = last_s.time

	if (p_v.last_sym) {
		s.next = p_v.last_sym.next
		if (s.next)
			s.next.prev = s;
		p_v.last_sym.next = s;
		s.prev = p_v.last_sym
	}
	p_v.last_sym = s;

	lktsym(s, last_s)
}

/* -- init the symbols at start of a music line -- */
function init_music_line() {
   var	p_voice, s, s1, s2, s3, last_s, v, st, shr, shrmx, shl,
	shlp, p_st, top,
	nv = voice_tb.length,
	fmt = tsfirst.fmt

	/* initialize the voices */
	for (v = 0; v < nv; v++) {
		if (!cur_sy.voices[v])
			continue
		p_voice = voice_tb[v];
		p_voice.st = cur_sy.voices[v].st
		p_voice.second = cur_sy.voices[v].second;
		p_voice.last_sym = p_voice.sym;

	// move the first clefs, key signatures and time signatures
	// to the staves
	   for (s = p_voice.sym; s && s.time == tsfirst.time; s = s.next) {
		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 = cfmt.writefields.indexOf('M') >= 0
					&& s.a_meter.length
				break
			}
			if (s.part)
				s.next.part = s.part
			unlksym(s)
			// fall thru
		case C.TEMPO:
		case C.BLOCK:
		case C.REMARK:
			continue
		}
		break
	    }
	}

	// generate the starting clefs, key signatures and time signatures

	// add a clef at start of the main voices
	last_s = tsfirst
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		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
		if (!staff_tb[st]
		 || !staff_tb[st].clef)
			continue
		s = clone(staff_tb[st].clef);
		s.v = v;
		s.p_v = p_voice;
		s.st = st;
		s.time = tsfirst.time;
		s.prev = null;
		s.next = p_voice.sym
		if (s.next)
			s.next.prev = 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;
		} else {
			s.ts_prev.ts_next = s
			delete s.seqst
		}
		if (last_s)
			last_s.ts_prev = s
		delete s.clef_small;
		delete s.part
		s.second = cur_sy.voices[v].second
// (fixme: needed for sample5 X:3 Fugue & staffnonote.html)
		if (!cur_sy.st_print[st])
			s.invis = true
		else if (!s.clef_none)
			delete s.invis
		s.fmt = fmt
	}

	/* add keysig */
	for (v = 0; v < nv; v++) {
		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]
		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
			delete s.part
			s.k_old_sf = s2.k_sf	// no key cancel
			s.fmt = fmt
		}
	}

	/* add time signature (meter) if needed */
	if (insert_meter) {
		for (v = 0; v < nv; v++) {
			p_voice = voice_tb[v];
			s2 = p_voice.meter
			if (!cur_sy.voices[v]
			 || cur_sy.voices[v].second
			 || !cur_sy.st_print[cur_sy.voices[v].st])
//			 || !s2.a_meter.length)
				continue
			s = clone(s2)
			new_sym(s, p_voice, last_s)
			delete s.part
			s.fmt = fmt
		}
		insert_meter = false		// no meter any more
	}

	// add an invisible bar for the various continued elements
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		if (p_voice.sls.length) {
			s = {
				type: C.BAR,
				fname: last_s.fname,
				bar_type: "|",
				dur: 0,
				multi: 0,
				invis: true,
				sls: p_voice.sls,
				fmt: fmt
			}
			new_sym(s, p_voice, last_s)
			p_voice.sls = []
		}
	}

	// 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

		// check if bracket stop at this time
		for (s = last_s; s && s.time == last_s.time; s = s.ts_next) {
			if (s.rbstop) {
				s2 = null
				break
			}
		}

		if (!s2)
			continue
		if (!cur_sy.voices[v]
		 || !cur_sy.st_print[cur_sy.voices[v].st])
			continue

		if (p_voice.last_sym.type == C.BAR) {
			if (!p_voice.last_sym.rbstop)
				p_voice.last_sym.rbstart = 1
		} else {
			new_sym(s2, p_voice, last_s)
			s2.fmt = fmt
		}
	}

	// compute the spacing of the added symbols
	self.set_pitch(last_s);

	s = tsfirst
	s.seqst = true

	for (s = last_s; s.ts_next && !s.ts_next.seqst; s = s.ts_next)
		;
	if (s.ts_next		// a bit further in case different keys per voice
	 && s.ts_next.type != C.CLEF	// (the clef may move in allsymwidth)
	 && !s.tp			// (start of a tuplet)
	 && !s.ts_next.a_ly)		// (don't update next .shrink)
		for (s = s.ts_next; s.ts_next && !s.ts_next.seqst; s = s.ts_next)
			;
	s2 = s.ts_next
	s.ts_next = null
	set_allsymwidth()
	s.ts_next = s2
} // init_music_line()

// 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.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 */

	// adjust the duration of the notes in a decoration !trem1..4!
	function trem_adj(s) {
		s.prev.trem2 = true
		s.prev.head = ++s.head
		if (--s.nflags > 0) {
			s.nflags += s.ntrem
		} else {
			if (s.nflags <= -2) {
				s.stemless = true
				s.prev.stemless = true
			}
			s.nflags = s.ntrem
		}
		s.prev.nflags = s.nflags
	} // trem_adj()

	for (s = p_voice.sym; s; s = s.next) {
		if (s.type == C.NOTE) {
			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:
			res = s.fmt.bardef[s.bar_type]
			if (res)
				s.bar_type = res
			if (!s.beam_on)
				start_flag = true
			if (!s.next && s.prev
			 && !s.invis
			 && s.prev.head == C.OVALBARS)
				s.prev.head = C.SQUARE
			break
		case C.GRACE:
			for (s2 = s.extra; s2; s2 = s2.next) {
				s2.notes.sort(abc2svg.pitcmp)
				res = identify_note(s2, s2.dur_orig)
				s2.head = res[0]
				s2.dots = res[1]
				s2.nflags = res[2]
				if (s2.trem2
				 && (!s2.next || s2.next.trem2))
					trem_adj(s2)
			}
			break
		case C.NOTE:
		case C.REST:
			res = identify_note(s, s.dur_orig);
			s.head = res[0];
			s.dots = res[1];
			s.nflags = res[2]
			if (s.nflags <= -2)
				s.stemless = true

			if (s.xstem)
				s.nflags = 0	// beam break
			if (s.trem1) {
				if (s.nflags > 0)
					s.nflags += s.ntrem
				else
					s.nflags = s.ntrem
			}
			if (s.next && s.next.trem2)
				break
			if (s.trem2) {
				trem_adj(s)
				break
			}

			nflags = s.nflags

			if (s.ntrem)
				nflags -= s.ntrem
			if (s.type == C.REST && s.beam_end
			 && !s.beam_on) {
//				s.beam_end = false;
				start_flag = true
			}
			if (start_flag
			 || nflags <= 0) {
				if (lastnote) {
					lastnote.beam_end = true;
					lastnote = null
				}
				if (nflags <= 0) {
					s.beam_st = s.beam_end = true
				} 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 || s.beam_on)
				lastnote = s
			break
		}
		if (s.type == C.NOTE) {
			if (s.nhd)
				s.notes.sort(abc2svg.pitcmp)
			pitch = s.notes[0].pit
//			if (s.prev
//			 && s.prev.type != C.NOTE) {
//				s.prev.notes[0].pit = (s.prev.notes[0].pit
//						    + pitch) / 2
			for (s2 = s.prev; s2; s2 = s2.prev) {
				if (s2.type != C.REST)
					break
				s2.notes[0].pit = pitch
			}
		} else {
			if (!s.notes) {
				s.notes = []
				s.notes[0] = {}
				s.nhd = 0
			}
			s.notes[0].pit = pitch
		}
	}
	if (lastnote)
		lastnote.beam_end = true
}

/* -- set the end of the repeat sequences -- */
function set_rb(p_voice) {
    var	s2, n,
	s = p_voice.sym

	while (s) {
		if (s.type != C.BAR || !s.rbstart || s.norepbra) {
			s = s.next
			continue
		}
		n = 0;
		s2 = null
		for (s = s.next; s; s = s.next) {
			if (s.type != C.BAR)
				continue
			if (s.rbstop)
				break
			if (!s.next) {
				s.rbstop = 2	// right repeat with end
				break
			}
			n++
			if (n == s.fmt.rbmin)
				s2 = s
			if (n == s.fmt.rbmax) {
				if (s2)
					s = s2;
				s.rbstop = 1	// right repeat without end
				break
			}
		}
	}
}

/* -- initialize the generator -- */
// this function is called only once per tune

var delpit = [0, -7, -14, 0]

function set_global() {
    var	p_voice, v,
	nv = voice_tb.length,
	sy = cur_sy,
	st = sy.nstaff

	insert_meter = cfmt.writefields.indexOf('M') >= 0

	/* get the max number of staves */
	while (1) {
		sy = sy.next
		if (!sy)
			break
		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 */
	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 */
	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
	set_clefs();
	self.set_pitch(null)
}

// get the left offsets of the first and other staff systems
// return left shift [no name, first music line, other lines
function get_lshift() {
    var	st, v, p_v, p1, po, fnt, w,
	sy = cur_sy,
	lsh = [0, 0, 0],
	nv = voice_tb.length

	// get the max width of a voice name/subname
	function get_wx(p, wx) {
	    var	w, j,
		i = 0

		p += '\n'
		while (1) {
			j = p.indexOf("\n", i)
			if (j < 0)
				break
			w = strwh(p.slice(i, j))[0] + 12
			if (w > wx)
				wx = w
			if (j < 0)
				break
			i = j + 1
		}
		return wx
	} // get_wx()

	for (v = 0; v < nv; v++) {
		p_v = voice_tb[v]
		p1 = p_v.nm
		po = p_v.snm
		if ((p1 || po) && !fnt) {
			set_font("voice")
			fnt = gene.deffont
		}
		if (p1) {
			w = get_wx(p1, lsh[1])
			if (w > lsh[1])
				lsh[1] = w
		}
		if (po) {
			w = get_wx(po, lsh[2])
			if (w > lsh[2])
				lsh[2] = w
		}
	}
	// add the width of the braces/brackets
	w = 0
	while (sy) {
		for (st = 0; st <= sy.nstaff; st++) {
			if (sy.staves[st].flags
					& (OPEN_BRACE2 | OPEN_BRACKET2)) {
				w = 12
				break
			}
			if (sy.staves[st].flags & (OPEN_BRACE | OPEN_BRACKET))
				w = 6
		}
		if (w == 12)
			break
		sy = sy.next
	}
	lsh[0] = w
	lsh[1] += w
	lsh[2] += w
	return lsh
} // get_lshift()

/* -- return the left indentation of the staves -- */
function set_indent(lsh) {
    var	st, v, w, p_voice, p, i, j, font,
	vnt = 0,
	fmt = tsnext ? tsnext.fmt : cfmt

	// name or subname?
	if (fmt.systvoices) {		// put the voice names in the staff system
	    for (v = voice_tb.length; --v >= 0; ) {
		p_voice = voice_tb[v]
		if (!cur_sy.voices[v]
		 || !gene.st_print[p_voice.st])
			continue
		if (p_voice.nm
		 && (fmt.systvoices == 1
		  || (p_voice.new_name && fmt.systvoices == 3))) {
			vnt = 1		// full name
			break
		}
		if (p_voice.snm)
			vnt = 2		// subname
	    }
	}
	gene.vnt = vnt			// voice name type for draw
	return lsh[vnt]
}

/* -- decide on beams and on stem directions -- */
/* this routine is called only once per tune */
function set_beams(sym) {
    var	s, t, g, beam, s_opp, n, m, mid_p, pu, pd,
	laststem = -1

	for (s = sym; s; s = s.next) {
		if (s.type != C.NOTE) {
			if (s.type != C.GRACE)
				continue
			g = s.extra
			if (g.stem == 2) {	/* opposite gstem direction */
				s_opp = s
				continue
			}
			if (!s.stem)
				s.stem = s.multi || 1
			for (; g; g = g.next) {
				g.stem = s.stem;
				g.multi = s.multi
			}
			continue
		}

		if (!s.stem && s.multi)
			s.stem = s.multi
		if (!s.stem) {			// if note alone on the staff
			mid_p = s.mid / 3 + 18

			/* notes in a beam have the same stem direction */
			if (beam) {
				s.stem = laststem
			} else if (s.beam_st && !s.beam_end) {	// beam start
				beam = true;

				// the stem direction is the one of the note
				// farthest from the middle line
						pu = s.notes[s.nhd].pit;
						pd = s.notes[0].pit
						for (g = s.next; g; g = g.next) {
							if (g.type != C.NOTE)
								continue
							if (g.stem || g.multi) // if forced direction
								s.stem = g.stem || g.multi
							if (g.notes[g.nhd].pit > pu)
								pu = g.notes[g.nhd].pit
							if (g.notes[0].pit < pd)
								pd = g.notes[0].pit
							if (g.beam_end)
								break
						}
					if (!s.stem && g.beam_end) {
							if (pu + pd < mid_p * 2) {
								s.stem = 1
							} else if (pu + pd > mid_p * 2) {
								s.stem = -1
							} else {
								if (s.fmt.bstemdown)
									s.stem = -1
							}
						}
				if (!s.stem)
					s.stem = laststem
			} else {				// no beam
				n = (s.notes[s.nhd].pit + s.notes[0].pit) / 2
				if (n == mid_p && s.nhd > 1) {
					for (m = 0; m < s.nhd; m++) {
						if (s.notes[m].pit >= mid_p)
							break
					}
					n = m * 2 < s.nhd ? mid_p - 1 : mid_p + 1
				}
				if (n < mid_p)
					s.stem = 1
				else if (n > mid_p || s.fmt.bstemdown)
					s.stem = -1
				else
					s.stem = laststem
			}
		} else {			/* stem set by set_stem_dir */
			if (s.beam_st && !s.beam_end)
				beam = true
		}
		if (s.beam_end)
			beam = false;
		laststem = s.stem;

		if (s_opp) {			/* opposite gstem direction */
			for (g = s_opp.extra; g; g = g.next)
				g.stem = -laststem;
			s_opp.stem = -laststem;
			s_opp = null
		}
	}
}

// check if there may be one head for unison when voice overlap
function same_head(s1, s2) {
    var	i1, i2, l1, l2, head, i11, i12, i21, i22, sh1, sh2,
	shu = s1.fmt.shiftunison || 0

	if (shu >= 3)
		return false
	if ((l1 = s1.dur) >= C.BLEN)
		return false
	if ((l2 = s2.dur) >= C.BLEN)
		return false
	if (s1.stemless && s2.stemless)
		return false
	if (s1.dots != s2.dots) {
		if (shu & 1
		 || s1.dots * s2.dots != 0)
			return false
	}
	if (s1.stem * s2.stem > 0)
		return false

	/* check if a common unison */
	i1 = i2 = 0
	if (s1.notes[0].pit > s2.notes[0].pit) {
//fixme:dots
		if (s1.stem < 0)
			return false
		while (s2.notes[i2].pit != s1.notes[0].pit) {
			if (++i2 > s2.nhd)
				return false
		}
	} else if (s1.notes[0].pit < s2.notes[0].pit) {
//fixme:dots
		if (s2.stem < 0)
			return false
		while (s2.notes[0].pit != s1.notes[i1].pit) {
			if (++i1 > s1.nhd)
				return false
		}
	}
	if (s2.notes[i2].acc != s1.notes[i1].acc)
		return false;
	i11 = i1;
	i21 = i2;
	sh1 = s1.notes[i1].shhd;
	sh2 = s2.notes[i2].shhd
	do {
//fixme:dots
		i1++;
		i2++
		if (i1 > s1.nhd) {
//fixme:dots
//			if (s1.notes[0].pit < s2.notes[0].pit)
//				return false
			break
		}
		if (i2 > s2.nhd) {
//fixme:dots
//			if (s1.notes[0].pit > s2.notes[0].pit)
//				return false
			break
		}
		if (s2.notes[i2].acc != s1.notes[i1].acc)
			return false
		if (sh1 < s1.notes[i1].shhd)
			sh1 = s1.notes[i1].shhd
		if (sh2 < s2.notes[i2].shhd)
			sh2 = s2.notes[i2].shhd
	} while (s2.notes[i2].pit == s1.notes[i1].pit)
//fixme:dots
	if (i1 <= s1.nhd) {
		if (i2 <= s2.nhd)
			return false
		if (s2.stem > 0)
			return false
	} else if (i2 <= s2.nhd) {
		if (s1.stem > 0)
			return false
	}
	i12 = i1;
	i22 = i2;

	head = 0
	if (l1 != l2) {
		if (l1 < l2) {
			l1 = l2;
			l2 = s1.dur
		}
		if (l1 < C.BLEN / 2) {
			if (s2.dots)
				head = 2
			else if (s1.dots)
				head = 1
		} else if (l2 < C.BLEN / 4) {	/* (l1 >= C.BLEN / 2) */
//			if (shu == 2)
//			 || s1.dots != s2.dots)
			if (shu & 2)
				return false
			head = s2.dur >= C.BLEN / 2 ? 2 : 1
		} else {
			return false
		}
	}
	if (!head)
		head = s1.p_v.scale < s2.p_v.scale ? 2 : 1
	if (head == 1) {
		for (i2 = i21; i2 < i22; i2++) {
			s2.notes[i2].invis = true
			delete s2.notes[i2].acc
		}
		for (i2 = 0; i2 <= s2.nhd; i2++)
			s2.notes[i2].shhd += sh1
	} else {
		for (i1 = i11; i1 < i12; i1++) {
			s1.notes[i1].invis = true
			delete s1.notes[i1].acc
		}
		for (i1 = 0; i1 <= s1.nhd; i1++)
			s1.notes[i1].shhd += sh2
	}
	if (s1.dots == s2.dots)			// let the dots in one voice
		s2.dots = 0
	return true
}

/* handle unison with different accidentals */
function unison_acc(s1, s2, i1, i2) {
    var	m, d, acc

	acc = s2.notes[i2].acc
	if (!acc) {
		d = w_note[s2.head] * 2 + s2.xmx + s1.notes[i1].shac + 2
		acc = s1.notes[i1].acc
		if (typeof acc == "object")	// microtone
			d += 2
		if (s2.dots)
			d += 6
		for (m = 0; m <= s1.nhd; m++) {
			s1.notes[m].shhd += d;
			s1.notes[m].shac -= d
		}
		s1.xmx += d
	} else {
		d = w_note[s1.head] * 2 + s1.xmx + s2.notes[i2].shac + 2
		if (typeof acc == "object")	// microtone
			d += 2
		if (s1.dots)
			d += 6
		for (m = 0; m <= s2.nhd; m++) {
			s2.notes[m].shhd += d;
			s2.notes[m].shac -= d
		}
		s2.xmx += d
		if (s1.notes[i1].acc)
			s1.notes[i1].shac -= 7
//fixme: why not dx_tb[s2.head] ?
	}
}

var MAXPIT = 48 * 2

/* set the left space of a note/chord */
function set_left(s) {
	var	m, i, j, shift,
		w_base = w_note[s.head],
		w = w_base,
		left = []

	for (i = 0; i < MAXPIT; i++)
		left.push(-100)

	/* stem */
	if (s.nflags > -2) {
		if (s.stem > 0) {
			w = -w;
			i = s.notes[0].pit * 2;
			j = (Math.ceil((s.ymx - 2) / 3) + 18) * 2
		} else {
			i = (Math.ceil((s.ymn + 2) / 3) + 18) * 2;
			j = s.notes[s.nhd].pit * 2
		}
		if (i < 0)
			i = 0
		if (j >= MAXPIT)
			j = MAXPIT - 1
		while (i <= j)
			left[i++] = w
	}

	/* notes */
	shift = s.notes[s.stem > 0 ? 0 : s.nhd].shhd;	/* previous shift */
	for (m = 0; m <= s.nhd; m++) {
		w = -s.notes[m].shhd + w_base + shift;
		i = s.notes[m].pit * 2
		if (i < 0)
			i = 0
		else if (i >= MAXPIT - 1)
			i = MAXPIT - 2
		if (w > left[i])
			left[i] = w
		if (s.head != C.SQUARE)
			w -= 1
		if (w > left[i - 1])
			left[i - 1] = w
		if (w > left[i + 1])
			left[i + 1] = w
	}

	return left
}

/* set the right space of a note/chord */
function set_right(s) {
	var	m, i, j, k, shift,
		w_base = w_note[s.head],
		w = w_base,
		flags = s.nflags > 0 && s.beam_st && s.beam_end,
		right = []

	for (i = 0; i < MAXPIT; i++)
		right.push(-100)

	/* stem and flags */
	if (s.nflags > -2) {
		if (s.stem < 0) {
			w = -w;
			i = (Math.ceil((s.ymn + 2) / 3) + 18) * 2;
			j = s.notes[s.nhd].pit * 2;
			k = i + 4
		} else {
			i = s.notes[0].pit * 2;
			j = (Math.ceil((s.ymx - 2) / 3) + 18) * 2
		}
		if (i < 0)
			i = 0
		if (j > MAXPIT)
			j = MAXPIT
		while (i < j)
			right[i++] = w
	}

	if (flags) {
		if (s.stem > 0) {
			if (s.xmx == 0)
				i = s.notes[s.nhd].pit * 2
			else
				i = s.notes[0].pit * 2;
			i += 4
			if (i < 0)
				i = 0
			for (; i < MAXPIT && i <= j - 4; i++)
				right[i] = 11
		} else {
			i = k
			if (i < 0)
				i = 0
			for (; i < MAXPIT && i <= s.notes[0].pit * 2 - 4; i++)
				right[i] = 3.5
		}
	}

	/* notes */
	shift = s.notes[s.stem > 0 ? 0 : s.nhd].shhd	/* previous shift */
	for (m = 0; m <= s.nhd; m++) {
		w = s.notes[m].shhd + w_base - shift;
		i = s.notes[m].pit * 2
		if (i < 0)
			i = 0
		else if (i >= MAXPIT - 1)
			i = MAXPIT - 2
		if (w > right[i])
			right[i] = w
		if (s.head != C.SQUARE)
			w -= 1
		if (w > right[i - 1])
			right[i - 1] = w
		if (w > right[i + 1])
			right[i + 1] = w
	}

	return right
}

/* -- shift the notes horizontally when voices overlap -- */
/* this routine is called only once per tune */
function set_overlap() {
    var	s, s1, s2, s3, i, i1, i2, m, sd, t, dp,
	d, d2, dr, dr2, dx,
	left1, right1, left2, right2, right3, pl, pr,
	sy = cur_sy

	// invert the voices
	function v_invert() {
		s1 = s2;
		s2 = s;
		d = d2;
		pl = left1;
		pr = right1;
		dr2 = dr
	}

	for (s = tsfirst; s; s = s.ts_next) {
		if (s.type != C.NOTE
		 || s.invis) {
			if (s.type == C.STAVES)
				sy = s.sy
			continue
		}

		// set the dot vertical offset of secondary voices
		if (s.second)
			s.dot_low = 1 //true

		/* treat the stem on two staves with different directions */
		if (s.xstem
		 && s.ts_prev.stem < 0) {
			for (m = 0; m <= s.nhd; m++) {
				s.notes[m].shhd -= 7;		// stem_xoff
				s.notes[m].shac += 16
			}
		}

		/* search the next note at the same time on the same staff */
		s2 = s
		while (1) {
			s2 = s2.ts_next
			if (!s2)
				break
			if (s2.time != s.time) {
				s2 = null
				break
			}
			if (s2.type == C.NOTE
			 && !s2.invis
			 && s2.st == s.st)
				break
		}
		if (!s2)
			continue
		s1 = s

		/* no shift if no overlap */
		if (s1.ymn > s2.ymx
		 || s1.ymx < s2.ymn)
			continue

		if (same_head(s1, s2))
			continue

		// special case when only a second and no dots
	    if (!s1.dots && !s2.dots)
		if ((s1.stem > 0 && s2.stem < 0
		  && s1.notes[0].pit == s2.notes[s2.nhd].pit + 1)
		 || (s1.stem < 0 && s2.stem > 0
		  && s1.notes[s1.nhd].pit + 1 == s2.notes[0].pit)) {
			if (s1.stem < 0) {
				s1 = s2;
				s2 = s
			}
			d = s1.notes[0].shhd + 7
			for (m = 0; m <= s2.nhd; m++)	// shift the lower note(s)
				s2.notes[m].shhd += d
			s2.xmx += d
			s1.xmx = s2.xmx		// align the dots
			continue
		}

		/* compute the minimum space for 's1 s2' and 's2 s1' */
		right1 = set_right(s1);
		left2 = set_left(s2);

		s3 = s1.ts_prev
		if (s3 && s3.time == s1.time
		 && s3.st == s1.st && s3.type == C.NOTE && !s3.invis) {
			right3 = set_right(s3)
			for (i = 0; i < MAXPIT; i++) {
				if (right3[i] > right1[i])
					right1[i] = right3[i]
			}
		} else {
			s3 = null
		}
		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
		 && ((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)
			for (i = 0; i < MAXPIT; i++) {
				if (right3[i] > left1[i])
					left1[i] = right3[i]
			}
		}
		d2 = dr = dr2 = -100
		for (i = 0; i < MAXPIT; i++) {
			if (left1[i] + right2[i] > d2)
				d2 = left1[i] + right2[i]
			if (right2[i] > dr2)
				dr2 = right2[i]
			if (right1[i] > dr)
				dr = right1[i]
		}

		/* check for unison with different accidentals
		 * and clash of dots */
		t = 0;
		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) {
					t = -1
					break
				}
				if (s2.notes[i2].acc) {
					if (!s1.notes[i1].acc)
						s1.notes[i1].acc = s2.notes[i2].acc
					s2.notes[i2].acc = 0
				}
				if (s1.dots && s2.dots
				 && (s1.notes[i1].pit & 1))
					t = 1
				break
			case -1:
				if (s1.notes[i1].pit & 1)
					s2.dot_low = false
				else
					s1.dot_low = true
				break
			case -2:
				if (!(s1.notes[i1].pit & 1))
					s2.dot_low = false
				break
			}
			if (t < 0)
				break
			if (dp >= 0) {
				if (--i1 < 0)
					break
			}
			if (dp <= 0) {
				if (--i2 < 0)
					break
			}
		}

		if (t < 0) {	/* unison and different accidentals */
			unison_acc(s1, s2, i1, i2)
			continue
		}

		sd = 0;
		if (s1.dots) {
			if (!s2.dots
			 || !t)			// if no dot clash
				sd = 1		// align the dots
		} else if (s2.dots) {
			if (d2 + dr < d + dr2)
				sd = 1		/* align the dots */
		}
		pl = left2;
		pr = right2
		if (!s3 && d2 + dr < d + dr2)
			v_invert()
		d += 3
		if (d < 0)
			d = 0;			// (not return!)

		/* handle the previous shift */
		m = s1.stem >= 0 ? 0 : s1.nhd;
		d += s1.notes[m].shhd;
		m = s2.stem >= 0 ? 0 : s2.nhd;
		d -= s2.notes[m].shhd

		/*
		 * room for the dots
		 * - if the dots of v1 don't shift, adjust the shift of v2
		 * - otherwise, align the dots and shift them if clash
		 */
		if (s1.dots) {
			dx = 7.7 + s1.xmx +		// x 1st dot
				3.5 * s1.dots - 3.5 +	// x last dot
				3;			// some space
			if (!sd) {
				d2 = -100;
				for (i1 = 0; i1 <= s1.nhd; i1++) {
					i = s1.notes[i1].pit
					if (!(i & 1)) {
						if (!s1.dot_low)
							i++
						else
							i--
					}
					i *= 2
					if (i < 1)
						i = 1
					else if (i >= MAXPIT - 1)
						i = MAXPIT - 2
					if (pl[i] > d2)
						d2 = pl[i]
					if (pl[i - 1] + 1 > d2)
						d2 = pl[i - 1] + 1
					if (pl[i + 1] + 1 > d2)
						d2 = pl[i + 1] + 1
				}
				if (dx + d2 + 2 > d)
					d = dx + d2 + 2
			} else {
				if (dx < d + dr2 + s2.xmx) {
					d2 = 0
					for (i1 = 0; i1 <= s1.nhd; i1++) {
						i = s1.notes[i1].pit
						if (!(i & 1)) {
							if (!s1.dot_low)
								i++
							else
								i--
						}
						i *= 2
						if (i < 1)
							i = 1
						else if (i >= MAXPIT - 1)
							i = MAXPIT - 2
						if (pr[i] > d2)
							d2 = pr[i]
						if (pr[i - 1] + 1 > d2)
							d2 = pr[i - 1] = 1
						if (pr[i + 1] + 1 > d2)
							d2 = pr[i + 1] + 1
					}
					if (d2 > 4.5
					 && 7.7 + s1.xmx + 2 < d + d2 + s2.xmx)
						s2.xmx = d2 + 3 - 7.7
				}
			}
		}

		for (m = s2.nhd; m >= 0; m--) {
			s2.notes[m].shhd += d
//			if (s2.notes[m].acc
//			 && s2.notes[m].pit < s1.notes[0].pit - 4)
//				s2.notes[m].shac -= d
		}
		s2.xmx += d
		if (sd)
			s1.xmx = s2.xmx		// align the dots
	}
}

/* -- set the stem height -- */
/* this routine is called only once per tune */
// (possible hook)
Abc.prototype.set_stems = function() {
	var s, s2, g, slen, scale,ymn, ymx, nflags, ymin, ymax

	for (s = tsfirst; s; s = s.ts_next) {
		if (s.type != C.NOTE) {
			if (s.type != C.GRACE)
				continue
			ymin = ymax = s.mid
			for (g = s.extra; g; g = g.next) {
				slen = GSTEM
				if (g.nflags > 1)
					slen += 1.2 * (g.nflags - 1);
				ymn = 3 * (g.notes[0].pit - 18);
				ymx = 3 * (g.notes[g.nhd].pit - 18)
				if (s.stem >= 0) {
					g.y = ymn;
					g.ys = ymx + slen;
					ymx = Math.round(g.ys)
				} else {
					g.y = ymx;
					g.ys = ymn - slen;
					ymn = Math.round(g.ys)
				}
				ymx += 4
				ymn -= 4
				if (ymn < ymin)
					ymin = ymn
				else if (ymx > ymax)
					ymax = ymx;
				g.ymx = ymx;
				g.ymn = ymn
			}
			s.ymx = ymax;
			s.ymn = ymin
			continue
		}

		/* shift notes in chords (need stem direction to do this) */
		set_head_shift(s);

		/* if start or end of beam, adjust the number of flags
		 * with the other end */
		nflags = s.nflags
		if (s.beam_st && !s.beam_end) {
			if (s.feathered_beam)
				nflags = ++s.nflags
			for (s2 = s.next; /*s2*/; s2 = s2.next) {
				if (s2.type == C.NOTE) {
					if (s.feathered_beam)
						s2.nflags++
					if (s2.beam_end)
						break
				}
			}
/*			if (s2) */
			    if (s2.nflags > nflags)
				nflags = s2.nflags
		} else if (!s.beam_st && s.beam_end) {
//fixme: keep the start of beam ?
			for (s2 = s.prev; /*s2*/; s2 = s2.prev) {
				if (s2.beam_st)
					break
			}
/*			if (s2) */
			    if (s2.nflags > nflags)
				nflags = s2.nflags
		}

		/* set height of stem end */
		slen = s.fmt.stemheight
		switch (nflags) {
//		case 2: slen += 0; break
		case 3:	slen += 4; break
		case 4:	slen += 8; break
		case 5:	slen += 12; break
		}
		if ((scale = s.p_v.scale) != 1)
			slen *= (scale + 1) * .5;
		ymn = 3 * (s.notes[0].pit - 18)
		if (s.nhd > 0) {
			slen -= 2;
			ymx = 3 * (s.notes[s.nhd].pit - 18)
		} else {
			ymx = ymn
		}
		if (s.ntrem)
			slen += 2 * s.ntrem		/* tremolo */
		if (s.decstm != null) {			// if deco on the stem
			if (nflags <= 0) {
				if (slen < s.decstm + 6)
					slen = s.decstm + 6
			} else {
			    var	t = nflags * 4		// beams

				if (s.beam_st & s.beam_end)
					t += 2		// flags
				if (slen < s.decstm + 4 + t)
					slen = s.decstm + 4 + t
			}
		}
		if (s.stemless) {
			if (s.stem >= 0) {
				s.y = ymn;
				s.ys = ymx
			} else {
				s.ys = ymn;
				s.y = ymx
			}
			s.ymx = ymx + 4;
			s.ymn = ymn - 4
		} else if (s.stem >= 0) {
			if (s.notes[s.nhd].pit > 26
			 && (nflags <= 0
			  || !s.beam_st
			  || !s.beam_end)) {
				slen -= 2
				if (s.notes[s.nhd].pit > 28)
					slen -= 2
			}
			s.y = ymn
			if (s.notes[0].tie)
				ymn -= 3;
			s.ymn = ymn - 4;
			s.ys = ymx + slen
			if (s.ys < s.mid)
				s.ys = s.mid;
			s.ymx = (s.ys + 2.5) | 0
		} else {			/* stem down */
			if (s.notes[0].pit < 18
			 && (nflags <= 0
			  || !s.beam_st || !s.beam_end)) {
				slen -= 2
				if (s.notes[0].pit < 16)
					slen -= 2
			}
			s.ys = ymn - slen
			if (s.ys > s.mid)
				s.ys = s.mid;
			s.ymn = (s.ys - 2.5) | 0;
			s.y = ymx
/*fixme:the tie may be lower*/
			if (s.notes[s.nhd].tie)
				ymx += 3;
			s.ymx = ymx + 4
		}
	}
}

// generate a block symbol
var blocks = []		// array of delayed block symbols

// (possible hook)
Abc.prototype.block_gen = function(s) {
	switch (s.subtype) {
	case "leftmargin":
	case "rightmargin":
	case "pagescale":
	case "pagewidth":
	case "scale":
	case "staffwidth":
		self.set_format(s.subtype, s.param)
		break
	case "mc_start":		// multicol start
		if (multicol) {
			error(1, s, "No end of the previous %%multicol")
			break
		}
		multicol = {
			state: parse.state,
			posy: posy,
			maxy: posy,
			lm: cfmt.leftmargin,
			rm: cfmt.rightmargin,
			w: cfmt.pagewidth,
			sc: cfmt.scale
		}
		break
	case "mc_new":			// multicol new
		if (!multicol || multicol.state != parse.state) {
			error(1, s, "%%multicol new without start")
			break
		}
		if (posy > multicol.maxy)
			multicol.maxy = posy
		cfmt.leftmargin = multicol.lm
		cfmt.rightmargin = multicol.rm
		cfmt.pagewidth = multicol.w
		cfmt.scale = multicol.sc
		posy = multicol.posy
		img.chg = 1 //true
		break
	case "mc_end":			// multicol end
		if (!multicol || multicol.state != parse.state) {
			error(1, s, "%%multicol end without start")
			break
		}
		if (posy < multicol.maxy)
			posy = multicol.maxy
		cfmt.leftmargin = multicol.lm
		cfmt.rightmargin = multicol.rm
		cfmt.pagewidth = multicol.w
		cfmt.scale = multicol.sc
		multicol = undefined
		blk_flush()
		img.chg = 1 //true
		break
	case "ml":
		blk_flush()
		user.img_out(s.text)
		break
	case "newpage":
		if (!user.page_format)
			break
		blk_flush()
		if (blkdiv < 0)		// split the tune
			user.img_out('</div>')
		blkdiv = 2		// start the next SVG in a new page
		break
	case "sep":
		set_page();
		vskip(s.sk1);
		output += '<path class="stroke"\n\td="M';
		out_sxsy((img.width -s.l) / 2 - img.lm, ' ', 0)
		output += 'h' + s.l.toFixed(1) + '"/>\n';
		vskip(s.sk2);
		break
	case "text":
		set_font(s.font)
		use_font(s.font)
		write_text(s.text, s.opt)
		break
	case "title":
		write_title(s.text, true)
		break
	case "vskip":
		vskip(s.sk);
		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, 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]

		if (!p_staff)
			p_staff = staff_tb[st] = {}
		p_staff.y = 0;			// staff system not computed yet
		p_staff.stafflines = sy_staff.stafflines;
		p_staff.staffscale = sy_staff.staffscale;
		p_staff.ann_top = p_staff.ann_bot = 0
	} // reset_staff()

	// adjust the empty flag of brace systems
	function set_brace() {
		var	st, i, empty_fl,
			n = sy.staves.length

		// if a system brace has empty and non empty staves, keep all staves
		for (st = 0; st < n; st++) {
			if (!(sy.staves[st].flags & (OPEN_BRACE | OPEN_BRACE2)))
				continue
			empty_fl = 0;
			i = st
			while (st < n) {
				empty_fl |= non_empty[st] ? 1 : 2
				if (sy.staves[st].flags & (CLOSE_BRACE | CLOSE_BRACE2))
					break
				st++
			}
			if (empty_fl == 3) {	// if both empty and not empty staves
				while (i <= st) {
					non_empty[i] = true;
					non_empty_gl[i++] = true
				}
			}
		}
	} // set_brace()

	// set the top and bottom of the staves
	function set_top_bot() {
	    var	st, p_staff, i, 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

			l = p_staff.stafflines.length;
			p_staff.topbar = 6 * (l - 1)

			for (i = 0; i < l - 1; i++) {
				switch (p_staff.stafflines[i]) {
				case '.':
				case '-':
					continue
				}
				break
			}
			p_staff.botbar = i * 6
			if (i >= l - 2) {		// 0, 1 or 2 lines
				if (p_staff.stafflines[i] != '.') {
					p_staff.botbar -= 6;
					p_staff.topbar += 6
				} else {		// no line: big bar
					p_staff.botbar -= 12;
					p_staff.topbar += 12
					continue	// no helper line
				}
			}
			if (!non_empty_gl[st])
				continue
		}
	} // 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 = sy.nstaff
	for (st = 0; st <= nstaff; st++)
		reset_staff(st);
	non_empty = new Uint8Array(nstaff + 1)

	/*
	 * search the next end of line,
	 * and mark the empty staves
	 */
	for (s = tsfirst; s; s = s.ts_next) {
		if (s.nl)
			break
		switch (s.type) {
		case C.STAVES:
			set_brace();
			sy.st_print = non_empty
			sy = s.sy;
			while (nstaff < sy.nstaff)
				reset_staff(++nstaff)
			non_empty = new Uint8Array(nstaff + 1)
			continue

		// the block symbols will be treated after music line generation
		case C.BLOCK:
			if (!s.play) {
				blocks.push(s)
				unlksym(s)
			} else if (s.ts_next && s.ts_next.shrink)
				s.ts_next.shrink = 0
			continue
		}
		st = s.st
		if (st > nstaff) {
			switch (s.type) {
			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.BAR:
			if (s.bar_mrep
			 || sy.staves[st].staffnonote > 1)
				break
			continue
		case C.GRACE:
			break
		case C.NOTE:
		case C.REST:
		case C.SPACE:
		case C.MREST:
			if (sy.staves[st].staffnonote > 1)
				break
			if (s.invis)
				continue
			if (sy.staves[st].staffnonote
			 || s.type == C.NOTE)
				break
			continue
		}
		non_empty_gl[st] = non_empty[st] = true
	}
	tsnext = s;

	/* set the last empty staves */
	set_brace()
	sy.st_print = non_empty

	/* define the offsets of the measure bars */
	set_top_bot()

	// if not the end of the tune, set the end of the music line
	if (tsnext) {
		s = tsnext;
		delete s.nl;
		last = s.ts_prev;
		last.ts_next = null;

		// and the end of the voices
		nv = voice_tb.length
		for (v = 0; v < nv; v++) {
			p_voice = voice_tb[v]
			if (p_voice.sym
			 && p_voice.sym.time <= tsnext.time) {
				for (s = last; s; s = s.ts_prev) {
					if (s.v == v) {
						p_voice.s_next = s.next;
						s.next = null;
						break
					}
				}
				if (s)
					continue
			}
			p_voice.s_next = p_voice.sym;
			p_voice.sym = null
		}
	}

	// initialize the music line
	init_music_line()

	// keep the array of the staves to be printed
	gene.st_print = non_empty_gl
}

/* -- position the symbols along the staff -- */
// (possible hook)
Abc.prototype.set_sym_glue = function(width) {
    var	g, x, some_grace, stretch,
	cnt = 4,
	xmin = 0,		// sigma shrink = minimum spacing
	xx = 0,			// sigma natural spacing
	xs = 0,			// sigma unexpandable elements with no space
	xse = 0,		// sigma unexpandable elements with space
	ll = !tsnext ||		// last line? yes
		(tsnext.type == C.BLOCK	// no, but followed by %%command
		 && !tsnext.play)
		|| blocks.length,	//	(abcm2ps compatibility)
	s = tsfirst,
	spf = 1,		// spacing factor
	xx0 = 0

	/* calculate the whole space of the symbols */
	for ( ; s; s = s.ts_next) {
		if (s.type == C.GRACE && !some_grace)
			some_grace = s
		if (s.seqst) {
			xmin += s.shrink
			if (xmin > width) {
			    if (cfmt.singleline)
				width = xmin
			    else
				error(1, s, "Line too much shrunk $1 $2 $3",
					xmin.toFixed(1),
					xx.toFixed(1),
					width.toFixed(1))
//				break
			}
			if (s.space) {
				if (s.space < s.shrink) {
					xse += s.shrink;
					xx += s.shrink
				} else {
//					xx += s.space * spf + s.shrink * (1 - spf)
					xx += s.space
					xx0 += s.shrink
				}
			} else {
				xs += s.shrink
			}
		}
	}

	// can occur when bar alone in a staff system
	if (!xx) {
		realwidth = 0
		return
	}

	// stretch or not?
	s = tsfirst

	if (ll) {
		if ((xx - xx0 + xs) / width > (1 - s.fmt.stretchlast))
			stretch = 1 //true
	} else if (s.fmt.stretchstaff) {
		stretch = 1 //true
	}

	// strong shrink
	if (xmin >= width) {
		x = 0
		for ( ; s; s = s.ts_next) {
			if (s.seqst)
				x += s.shrink;
			s.x = x
		}
//		realwidth = width
		spf_last = .65
	} else {
		if (stretch) {
			if (xx == xse)			// if no space
				xx += 10
			spf = (width - xs - xse) / (xx - xse)
		} else {
			spf = spf_last
			if (ll && spf < s.fmt.stretchlast)
				spf = s.fmt.stretchlast
			else if (!ll)
				spf = 1 - cfmt.maxshrink * .75
			if (spf > (width - xs) / xx)
				spf = (width - xs) / xx
		}
		while (--cnt >= 0) {
			xx = 0;
			xse = 0;
			x = 0
			for (s = tsfirst; s; s = s.ts_next) {
				if (s.seqst) {
					if (s.space) {
						if (s.space * spf <= s.shrink) {
							xse += s.shrink;
							xx += s.shrink;
							x += s.shrink
						} else {
							xx += s.space;
							x += s.space * spf
						}
					} else {
						x += s.shrink
					}
				}
				s.x = x
			}
			if (!stretch && x < width)
				break
			if (Math.abs(x - width) < 0.1)
				break
			if (xx == xse)			// if no space
				xx += 10
			spf = (width - xs - xse) / (xx - xse)
		}
		spf_last = spf
	}
	realwidth = x

	/* set the x offsets of the grace notes */
	for (s = some_grace; s; s = s.ts_next) {
		if (s.type != C.GRACE)
			continue
		if (s.gr_shift)
			x = s.prev.x + s.prev.wr
		else
			x = s.x - s.wl
		for (g = s.extra; g; g = g.next)
			g.x += x
	}
}

// set the starting symbols of the voices for the new music line
function set_sym_line() {
    var	p_v, s,
	v = voice_tb.length

	// set the first symbol of each voice
	while (--v >= 0) {
		p_v = voice_tb[v]
		if (p_v.sym && p_v.s_prev) {
			p_v.sym.prev = p_v.s_prev
			p_v.s_prev.next = p_v.sym
		}
		s = p_v.s_next			// (set in set_piece)
		p_v.s_next = null
		p_v.sym = s
		if (s) {
			if (s.prev)
				s.prev.next = s
			p_v.s_prev = s.prev	// (save for play)
			s.prev = null
		} else {
			p_v.s_prev = null
		}
	}
}

// set the left offset the images
function set_posx() {
	posx = img.lm / cfmt.scale
}

// initialize the start of generation / new music line
// and output the inter-staff blocks if any
function gen_init() {
	var	s = tsfirst,
		tim = s.time

	for ( ; s; s = s.ts_next) {
		if (s.time != tim) {
			set_page()
			return
		}
		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
			continue
		case C.BLOCK:
			if (s.play)
				continue	// keep for play
			self.block_gen(s)
			break
		}
		unlksym(s)
		if (s.p_v.s_next == s)
			s.p_v.s_next = s.next
	}
	tsfirst = null			/* no more notes */
}

/* -- generate the music -- */
// (possible hook)
Abc.prototype.output_music = function() {
    var v, lwidth, indent, lsh, line_height, ts1st, tslast, p_v, meter1,
	nv = voice_tb.length

	set_global()
	if (nv > 1)			// if many voices
		self.set_stem_dir()	// set the stems direction in 'multi'

	for (v = 0; v < nv; v++)
		set_beams(voice_tb[v].sym);	/* decide on beams */

	self.set_stems()		// set the stem lengths

	set_acc_shft()			// set the horizontal offset of accidentals
	if (nv > 1) {			// if many voices
		set_rest_offset();	/* set the vertical offset of rests */
		set_overlap();		/* shift the notes on voice overlap */
	}
	set_allsymwidth(1)		// set the width of all symbols

	// output the blocks and define the page layout
	gen_init()
	if (!tsfirst)
		return

	lsh = get_lshift()

	/* if single line, adjust the page width */
	if (cfmt.singleline) {
		v = get_ck_width();
		lwidth = lsh[0] + v[0] + v[1] + get_width(tsfirst, null)[0]
		v = cfmt.singleline == 2	// if as wide as the page width
			? get_lwidth() : lwidth
		if (v > lwidth)
			lwidth = v
		else
			img.width = lwidth * cfmt.scale + img.lm + img.rm + 2
	} else {

	/* else, split the tune into music lines */
		lwidth = get_lwidth();
		cut_tune(lwidth, lsh)
	}

	// save symbol pointers for play
	ts1st = tsfirst
	v = nv
	while (--v >= 0)
		voice_tb[v].osym = voice_tb[v].sym
	meter1 = ts1st.p_v.meter

	spf_last = .65				// last spacing factor
	while (1) {				/* loop per music line */
		set_piece();
		indent = set_indent(lsh)
		if (!line_height
		 && cfmt.indent
		 && indent < cfmt.indent)
		 	indent = cfmt.indent
		self.set_sym_glue(lwidth - indent)
		if (realwidth) {
			if (img.wx < realwidth)
				img.wx = realwidth
			if (indent) {
				img.wx += indent
				posx += indent
			}
			draw_sym_near();		// delayed output
			line_height = set_staff();
		    if (line_height) {			// if some music
			draw_systems(indent);
			draw_all_sym();
			delayed_update();
				vskip(line_height)
		    }
			if (indent)
				posx -= indent;
		}

		blk_flush()
		while (blocks.length)
			self.block_gen(blocks.shift())
		if (tslast)
			tslast.ts_next.ts_prev = tslast
		if (!tsnext)
			break
		tsnext.ts_prev.ts_next =		// (restore for play)
			tsfirst = tsnext

		// next line
		gen_init()
		if (!tsfirst)
			break
		tslast = tsfirst.ts_prev
		tsfirst.ts_prev = null;
		set_sym_line();
		lwidth = get_lwidth()	// the image size may have changed
	}

	// restore for play
//--fixme: no, good links, but playback crashes!!!
//	tsfirst = ts1st.ts_next				// skip staves
	tsfirst = ts1st
	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
	}
	ts1st.p_v.meter = meter1
}
// abc2svg - parse.js - ABC parse
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

var	a_gch,		// array of parsed guitar chords
	a_dcn = [],	// array of parsed decoration names
	multicol,	// multi column object
	maps = {}	// maps object - see set_map()
var	qplet_tb = new Int8Array([ 0, 1, 3, 2, 3, 0, 2, 0, 3, 0 ]),
	ntb = "CDEFGABcdefgab"

// set the source references of a symbol
function set_ref(s) {
	s.fname = parse.fname;
	s.istart = parse.istart;
	s.iend = parse.iend
}

// -- %% pseudo-comment

// clef definition (%%clef, K: and V:)
function new_clef(clef_def) {
	var	s = {
			type: C.CLEF,
			clef_line: 2,
			clef_type: "t",
			v: curvoice.v,
			p_v: curvoice,
			time: curvoice.time,
			dur: 0,
			clef_small : 1 //true
		},
		i = 1

	set_ref(s)

	switch (clef_def[0]) {
	case '"':
		i = clef_def.indexOf('"', 1);
		s.clef_name = clef_def.slice(1, i);
		i++
		break
	case 'a':
		if (clef_def[1] == 'u') {	// auto
			s.clef_type = "a";
			s.clef_auto = true;
			i = 4
			break
		}
		i = 4				// alto
	case 'C':
		s.clef_type = "c";
		s.clef_line = 3
		break
	case 'b':				// bass
		i = 4
	case 'F':
		s.clef_type = "b";
		s.clef_line = 4
		break
	case 'n':				// none
		i = 4
		s.invis = true
		s.clef_none = 1 //true
		break
	case 't':
		if (clef_def[1] == 'e') {	// tenor
			s.clef_type = "c";
			s.clef_line = 4
			break
		}
		i = 6
	case 'G':
//		s.clef_type = "t"		// treble
		break
	case 'p':
		i = 4
	case 'P':				// perc
		s.clef_type = "p";
		s.clef_line = 3;
		break
	default:
		syntax(1, "Unknown clef '$1'", clef_def)
		return //undefined
	}
	if (clef_def[i] >= '1' && clef_def[i] <= '9') {
		s.clef_line = +clef_def[i]
		i++
	}

	// handle the octave (+/-8 - ^/_8)
	delete curvoice.snd_oct
	if (clef_def[i + 1] != '8'
	 && clef_def[i + 1] != '1')
		return s
	switch (clef_def[i]) {			// octave
	case '^':
		s.clef_oct_transp = true
	case '+':
		s.clef_octave = clef_def[i + 1] == '8' ? 7 : 14
		if (!s.clef_oct_transp)		// MIDI higher octave
			curvoice.snd_oct = clef_def[i + 1] == 8 ? 12 : 24
		break
	case '_':
		s.clef_oct_transp = true
	case '-':
		s.clef_octave = clef_def[i + 1] == '8' ? -7 : -14
		if (!s.clef_oct_transp)		// MIDI lower octave
			curvoice.snd_oct = clef_def[i + 1] == 8 ? -12 : -24
		break
	}
	return s
}

// convert an interval to a base-40 interval
function get_interval(param, score) {
    var	i, val, tmp, note, pit

	tmp = new scanBuf;
	tmp.buffer = param
	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, errs.bad_transp)
				return
			}
			pit[i] = 242			// 'c' (C5)
		} else {
			if (typeof note.acc == "object") {
				syntax(1, errs.bad_transp)
				return
			}
			pit[i] = abc2svg.pab40(note.pit, note.acc)
		}
	}
	return pit[1] - pit[0]
}

// transpose a note for the score
// Return the transposed real accidental
function nt_trans(nt,
		  a) {			// real accidental
    var	ak, an, d, b40, n

	if (typeof a == "object") {		// if microtonal accidental
		n = a[0]			// numerator
		d = a[1]			// denominator
		a = n > 0 ? 1 : -1		// base accidental for transpose
	}

	b40 = abc2svg.pab40(nt.pit, a)
		+ curvoice.tr_sco		// base-40 transposition

	nt.pit = abc2svg.b40p(b40)		// new pitch
	an = abc2svg.b40a(b40)			// new accidental

	if (!d) {				// if not a microtonal accidental
		if (an == -3)			// if triple sharp/flat
			return an
		if (a && !an)
			an = 3			// needed for %%map
		a = an
		if (!nt.acc			// if no old accidental
		 && !curvoice.ckey.k_none)	// and normal key
			a = 0			// no accidental
		nt.acc = a
		return an
	}

	// set the microtonal accidental after transposition
	switch (an) {
	case -2:
		if (n > 0)
			n -= d * 2
		else
			n -= d
		break
	case -1:
		if (n > 0)
			n -= d
		break
	case 0:
	case 3:
		if (n > 0)
			n -= d
		else
			n += d
		break
	case 1:
		if (n < 0)
			n += d
		break
	case 2:
		if (n < 0)
			n += d * 2
		else
			n += d
		break
	}
	nt.acc = [n, d]
	return an
} // nt_trans()

// set the linebreak character
function set_linebreak(param) {
	var i, item

	for (i = 0; i < 128; i++) {
		if (char_tb[i] == "\n")
			char_tb[i] = nil	// remove old definition
	}
	param = param.split(/\s+/)
	for (i = 0; i < param.length; i++) {
		item = param[i]
		switch (item) {
		case '!':
		case '$':
		case '*':
		case ';':
		case '?':
		case '@':
			break
		case "<none>":
			continue
		case "<EOL>":
			item = '\n'
			break
		default:
			syntax(1, "Bad value '$1' in %%linebreak - ignored",
					item)
			continue
		}
		char_tb[item.charCodeAt(0)] = '\n'
	}
}

// set a new user character (U: or %%user)
function set_user(parm) {
    var	k, c, v,
	a = parm.match(/(.)[=\s]*(\[I:.+\]|".+"|!.+!)$/)

	if (!a) {
		syntax(1, 'Lack of starting [, ! or " in U: / %%user')
		return
	}
	c = a[1];
	v = a[2]
	if (c[0] == '\\') {
		if (c[1] == 't')
			c = '\t'
		else if (!c[1])
			c = ' '
	}

	k = c.charCodeAt(0)
	if (k >= 128) {
		syntax(1, errs.not_ascii)
		return
	}
	switch (char_tb[k][0]) {
	case '0':			// nil
	case 'd':
	case 'i':
	case ' ':
		break
	case '"':
	case '!':
	case '[':
		if (char_tb[k].length > 1)
			break
		// fall thru
	default:
		syntax(1, "Bad user character '$1'", c)
		return
	}
	switch (v) {
	case "!beambreak!":
		v = " "
		break
	case "!ignore!":
		v = "i"
		break
	case "!nil!":
	case "!none!":
		v = "d"
		break
	}
	char_tb[k] = v
}

// get a stafflines value
function get_st_lines(param) {
	if (!param)
		return
	if (/^[\]\[|.':-]+$/.test(param))	// '
		return param.replace(/\]/g, '[')

    var	n = +param
	switch (n) {
	case 0: return "..."
	case 1: return "..|"
	case 2: return ".||"
	case 3: return ".|||"
	}
	if (isNaN(n) || n < 0 || n > 16)
		return //undefined
	return "||||||||||||||||".slice(0, n)
}

// create a block symbol in the tune body
function new_block(subtype) {
    var	s = {
			type: C.BLOCK,
			subtype: subtype,
			dur: 0
		}

	sym_link(s)
	return s
}

// set the voice parameters
// (possible hook)
Abc.prototype.set_vp = function(a) {
    var	s, item, pos, val, clefpit,
	tr_p = 0

	while (1) {
		item = a.shift()
		if (!item)
			break
		if (item.slice(-1) == '='
		 && !a.length) {
			syntax(1, errs.bad_val, item)
			break
		}
		switch (item) {
		case "clef=":
			s = a.shift()		// keep last clef
			break
		case "clefpitch=":
			item = a.shift()		// (<note><octave>)
			if (item) {
				val = ntb.indexOf(item[0])
				if (val >= 0) {
					switch (item[1]) {
					case "'":
						val += 7
						break
					case ',':
						val -= 7
						if (item[2] == ',')
							val -= 7
						break
					}
					clefpit = 4 - val	// 4 = 'G'
					break
				}
			}
			syntax(1, errs.bad_val, item)
			break
		case "octave=":
			val = +a.shift()
			if (isNaN(val))
				syntax(1, errs.bad_val, item)
			else
				curvoice.octave = val
			break
		case "cue=":
			// (ignore cue=off)
//			curvoice.scale = a.shift() == 'on' ? .7 : 1
			if (a.shift() == 'on')
				curvoice.scale = .7
			break
		case "instrument=":

			// instrument=M/N => score=MN and sound=cN
			// (instrument=M == instrument=M/M)
			item = a.shift()
			val = item.indexOf('/')
			if (val < 0) {
				val = get_interval('c' + item)
				if (val == undefined)
					break
				curvoice.sound = val
				tr_p |= 2
				val = 0
			} else {
				val = get_interval('c' + item.slice(val + 1))
				if (val == undefined)
					break
				curvoice.sound = val
				tr_p |= 2
				val = get_interval(item.replace('/', ''))
				if (val == undefined)
					break
			}
			curvoice.score = cfmt.sound ? curvoice.sound : val
			tr_p |= 1
			break
		case "map=":			// %%voicemap
			curvoice.map = a.shift()
			break
		case "name=":
		case "nm=":
			curvoice.nm = a.shift()
			if (curvoice.nm[0] == '"')
				curvoice.nm = cnv_escape(curvoice.nm.slice(1, -1))
			curvoice.new_name = true
			break
		case "stem=":			// compatibility
		case "pos=":			// from %%pos only
			if (item == "pos=")
				item = a.shift()
					.slice(1, -1)	// always inside dble quotes
					.split(' ')
			else
				item = ["stm", a.shift()];
			val = posval[item[1]]
			if (val == undefined) {
				syntax(1, errs.bad_val, "%%pos")
				break
			}
			switch (item[2]) {
			case "align": val |= C.SL_ALIGN; break
			case "center": val |= C.SL_CENTER; break
			case "close": val |= C.SL_CLOSE; break
			}
			if (!pos)
				pos = {}
			pos[item[0]] = val
			break
		case "scale=":			// %%voicescale
			val = +a.shift()
			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
			val = get_interval(item, true)
			if (val != undefined) {
				curvoice.score = val
				tr_p |= 1
			}
			break
		case "shift=":
			if (cfmt.nedo) {
				syntax(1, errs.notransp)
				break
			}
			val = get_interval(a.shift())
			if (val != undefined) {
				curvoice.shift = val
				tr_p = 3
			}
			break
		case "sound=":
			if (cfmt.nedo) {
				syntax(1, errs.notransp)
				break
			}
// concert-score display: apply sound=
// sounding-score display: apply sound= only if M != c/C
// sound: apply sound=
			val = get_interval(a.shift())
			if (val == undefined)
				break
			curvoice.sound = val
			if (cfmt.sound)
				curvoice.score = val
			tr_p |= 2
			break
		case "subname=":
		case "sname=":
		case "snm=":
			curvoice.snm = a.shift()
			if (curvoice.snm[0] == '"')
				curvoice.snm = curvoice.snm.slice(1, -1);
			break
		case "stafflines=":
			val = get_st_lines(a.shift())
			if (val == undefined) {
				syntax(1, "Bad %%stafflines value")
				break
			}
			if (curvoice.st != undefined)
				par_sy.staves[curvoice.st].stafflines = val
			curvoice.stafflines = val
			break
		case "staffnonote=":
			val = +a.shift()
			if (isNaN(val))
				syntax(1, "Bad %%staffnonote value")
			else
				curvoice.staffnonote = val
			break
		case "staffscale=":
			val = +a.shift()
			if (isNaN(val) || val < .3 || val > 2)
				syntax(1, "Bad %%staffscale value")
			else
				curvoice.staffscale = val
			break
		case "tacet=":
			val = a.shift()
			curvoice.tacet = val || undefined
			break
		case "transpose=":		// (abcMIDI compatibility)
			val = get_transp(a.shift())
			if (val == undefined) {
				syntax(1, errs.bad_transp)
			} else {
				curvoice.sound = val
				if (cfmt.sound)
					curvoice.score = val
				tr_p = 2
			}
			break
		default:
			switch (item.slice(0, 4)) {
			case "treb":
			case "bass":
			case "alto":
			case "teno":
			case "perc":
				s = item
				break
			default:
				if ("GFC".indexOf(item[0]) >= 0)
					s = item
				else if (item.slice(-1) == '=')
					a.shift()
				break
			}
			break
		}
	}
	if (pos) {
		curvoice.pos = clone(curvoice.pos)
		for (item in pos)
			if (pos.hasOwnProperty(item))
				curvoice.pos[item] = pos[item]
	}

	if (s) {
		s = new_clef(s)
		if (s) {
			if (clefpit)
				s.clefpit = clefpit
			get_clef(s)
		}
	}

	// if transposition
	if (tr_p & 2) {			// curvoice.tr_sco is set in key_trans()
		tr_p = (curvoice.sound | 0) + (curvoice.shift | 0)
		if (tr_p)
			curvoice.tr_snd = abc2svg.b40m(tr_p + 122) - 36
							// semi-tone interval
		else if (curvoice.tr_snd)
			curvoice.tr_snd = 0
		curvoice.tr_snd40 = tr_p		// (for play chords)
	}
} // set_vp()

// set the K: / V: parameters
function set_kv_parm(a) {	// array of items
	if (!curvoice.init) {	// add the global parameters if not done yet
		curvoice.init = true
		if (info.V) {
			if (info.V[curvoice.id])
				a = info.V[curvoice.id].concat(a)
			if (info.V['*'])
				a = info.V['*'].concat(a)
		}
	}
	if (a.length)
		self.set_vp(a)
} // set_kv_parm()

// memorize the K:/V: parameters
function memo_kv_parm(vid,	// voice ID (V:) / '*' (K:/V:*)
			a) {	// array of items
	if (!a.length)
		return
	if (!info.V)
		info.V = {}
	if (info.V[vid])
		Array.prototype.push.apply(info.V[vid], a)
	else
		info.V[vid] = a
}

// K: key signature
// return the key and the voice/clef parameters
function new_key(param) {
    var	i, key_end, c, tmp, note,
	sf = "FCGDAEB".indexOf(param[0]) - 1,
	mode = 0,
	s = {
		type: C.KEY,
		dur: 0
	}

	set_ref(s);

	// tonic
	i = 1
    if (sf < -1) {
	switch (param[0]) {
	case 'H':				// bagpipe
		key_end = true
		if (param[1].toLowerCase() != 'p') {
			syntax(1, "Unknown bagpipe-like key")
			break
		}
		s.k_bagpipe = param[1];
		sf = param[1] == 'P' ? 0 : 2;
		i++

		// initialize the temperament if not done yet
		if (!cfmt.temper)
	// detune in cents for just intonation in A
	// (from https://patrickmclaurin.com/wordpress/?page_id=2420)
	//  C    ^C     D    _E     E     F    ^F     G    _A     A    _B     B
	// 15.3 -14.0  -2.0 -10.0   1.9  13.3 -16.0 -31.8 -12.0   0.0  11.4   3.8
	// but 'A' bagpipe = 480Hz => raise Math.log2(480/440)*1200 = 151
			cfmt.temper = new Float32Array([
   //	1.66, 1.37, 1.49, 1.41, 1.53, 1.63, 1.35, 1.19, 1.39, 1.51, 1.62, 1.55
   //   C    ^C     D    _E     E     F    ^F     G    _A      A     _B      B
11.62, 12.55,
     1.66, 2.37, 3.49, 0,
     1.66, 2.37, 3.49, 4.41, 5.53, 0,
		 3.49, 4.41, 5.53, 6.63, 7.35,
		       4.41, 5.53, 6.63, 7.35, 8.19, 0,
				   6.63, 7.35, 8.19, 9.39, 10.51, 0,
					       8.19, 9.39, 10.51, 11.62, 12.55, 0,
							   10.51, 11.62, 12.55,
									     1.66, 1.66
			])
		break
	case 'P':
		syntax(1, "K:P is deprecated");
		sf = 0;
		s.k_drum = true;
		key_end = true
		break
	case 'n':				// none
		if (param.indexOf("none") == 0) {
			sf = 0;
			s.k_none = true;
			i = 4
			break
		}
		// fall thru
	default:
		s.k_map = []
		s.k_mode = 0
		return [s, info_split(param)]
	}
    }

	if (!key_end) {
		switch (param[i]) {
		case '#': sf += 7; i++; break
		case 'b': sf -= 7; i++; break
		}
		param = param.slice(i).trim()
		switch (param.slice(0, 3).toLowerCase()) {
		default:
			if (param[0] != 'm'
			 || (param[1] != ' ' && param[1] != '\t'
			  && param[1] != '\n')) {
				key_end = true
				break
			}
			// fall thru ('m')
		case "aeo":
		case "m":
		case "min": sf -= 3;
			mode = 5
			break
		case "dor": sf -= 2;
			mode = 1
			break
		case "ion":
		case "maj": break
		case "loc": sf -= 5;
			mode = 6
			break
		case "lyd": sf += 1;
			mode = 3
			break
		case "mix": sf -= 1;
			mode = 4
			break
		case "phr": sf -= 4;
			mode = 2
			break
		}
		if (!key_end)
			param = param.replace(/\w+\s*/, '')

		// [exp] accidentals
		if (param.indexOf("exp ") == 0) {
			param = param.replace(/\w+\s*/, '')
			if (!param)
				syntax(1, "No accidental after 'exp'");
			s.exp = 1 //true
		}
		c = param[0]
		if (c == '^' || c == '_' || c == '=') {
			s.k_a_acc = [];
			tmp = new scanBuf;
			tmp.buffer = param
			do {
				note = parse_acc_pit(tmp)
				if (!note)
					break
				s.k_a_acc.push(note);
				c = param[tmp.index]
				while (c == ' ')
					c = param[++tmp.index]
			} while (c == '^' || c == '_' || c == '=');
			param = param.slice(tmp.index)
		} else if (s.exp && param.indexOf("none") == 0) {
			sf = 0
			param = param.replace(/\w+\s*/, '')
		}
	}

	if (sf < -7 || sf > 7) {
		syntax(1, "Key with double sharps/flats")
		if (sf > 7)
			sf -= 12
		else
			sf += 12
	}
	s.k_sf = sf;

	// set the map of the notes with accidentals
	s.k_map = s.k_bagpipe && !sf
		? abc2svg.keys[9]		// implicit F# and C#
		: abc2svg.keys[sf + 7]
	if (s.k_a_acc) {
		s.k_map = Array.prototype.slice.call(s.k_map)	// simple Array
		i = s.k_a_acc.length			// (for micro-accidentals)
		while (--i >= 0) {
			note = s.k_a_acc[i]
			s.k_map[(note.pit + 19) % 7] = note.acc
		}
	}
	s.k_mode = mode

	// key note as base-40
	s.k_b40 = [1,24,7,30,13,36,19, 2 ,25,8,31,14,37,20,3][sf + 7]

	return [s, info_split(param)]
}

// M: meter
function new_meter(p) {
    var	p_v,
	s = {
			type: C.METER,
			dur: 0,
			a_meter: []
		},
		meter = {},
		val, v,
		m1 = 0, m2,
		i = 0, j,
		wmeasure,
		in_parenth;

	set_ref(s)

	if (p.indexOf("none") == 0) {
		i = 4;				/* no meter */
		wmeasure = 1;	// simplify measure numbering and C.MREST conversion
	} else {
		wmeasure = 0
		while (i < p.length) {
			if (p[i] == '=')
				break
			switch (p[i]) {
			case 'C':
				meter.top = p[i++]
				if (!m1) {
					m1 = 4;
					m2 = 4
				}
				break
			case 'c':
			case 'o':
				meter.top = p[i++]
				if (!m1) {
					if (p[i - 1] == 'c') {
						m1 = 2;
						m2 = 4	// c = 2/4
					} else {
						m1 = 3;
						m2 = 4	// o = 3/4
					}
					switch (p[i]) {
					case '|':
						m2 /= 2	// c| = 2/2, o| = 3/2
						break
					case '.':
						m1 *= 3;
						m2 *= 2	// c. = 6/8, o. = 9/8
						break
					}
				}
				break
			case '.':
			case '|':
				m1 = 0;
				meter.top = p[i++]
				break
			case '(':
				if (p[i + 1] == '(') {	/* "M:5/4 ((2+3)/4)" */
					in_parenth = true;
					meter.top = p[i++];
					s.a_meter.push(meter);
					meter = {}
				}
				j = i + 1
				while (j < p.length) {
					if (p[j] == ')' || p[j] == '/')
						break
					j++
				}
				if (p[j] == ')' && p[j + 1] == '/') {	/* "M:5/4 (2+3)/4" */
					i++		/* remove the parenthesis */
					continue
				}			/* "M:5 (2+3)" */
				/* fall thru */
			case ')':
				in_parenth = p[i] == '(';
				meter.top = p[i++];
				s.a_meter.push(meter);
				meter = {}
				continue
			default:
				if (p[i] <= '0' || p[i] > '9') {
					syntax(1, "Bad char '$1' in M:", p[i])
					return
				}
				m2 = 2;			/* default when no bottom value */
				meter.top = p[i++]
				for (;;) {
					while (p[i] >= '0' && p[i] <= '9')
						meter.top += p[i++]
					if (p[i] == ')') {
						if (p[i + 1] != '/')
							break
						i++
					}
					if (p[i] == '/') {
						i++;
						if (p[i] <= '0' || p[i] > '9') {
							syntax(1, "Bad char '$1' in M:", p[i])
							return
						}
						meter.bot = p[i++]
						while (p[i] >= '0' && p[i] <= '9')
							meter.bot += p[i++]
						break
					}
					if (p[i] != ' ' && p[i] != '+')
						break
					if (i >= p.length
					 || p[i + 1] == '(')	/* "M:5 (2/4+3/4)" */
						break
					meter.top += p[i++]
				}
				m1 = eval(meter.top.replace(/ /g, '+'))
				break
			}
			if (!in_parenth) {
				if (meter.bot)
					m2 = +meter.bot
				wmeasure += m1 * C.BLEN / m2
			}
			s.a_meter.push(meter);
			meter = {}
			while (p[i] == ' ')
				i++
			if (p[i] == '+') {
				meter.top = p[i++];
				s.a_meter.push(meter);
				meter = {}
			}
		}
	}
	if (p[i] == '=') {
		val = p.substring(++i).match(/^(\d+)\/(\d+)$/)
		if (!val) {
			syntax(1, "Bad duration '$1' in M:", p.substring(i))
			return
		}
		wmeasure = C.BLEN * val[1] / val[2]
	}
	if (!wmeasure) {
		syntax(1, errs.bad_val, 'M:')
		return
	}
	s.wmeasure = wmeasure

	if (cfmt.writefields.indexOf('M') < 0)
		s.a_meter = []

	if (parse.state != 3) {
		info.M = p;
		glovar.meter = s
		if (parse.state) {

			/* in the tune header, change the unit note length */
			if (!glovar.ulen) {
				if (wmeasure <= 1
				 || wmeasure >= C.BLEN * 3 / 4)
					glovar.ulen = C.BLEN / 8
				else
					glovar.ulen = C.BLEN / 16
			}
			for (v = 0; v < voice_tb.length; v++) {
				voice_tb[v].meter = s;
				voice_tb[v].wmeasure = wmeasure
			}
		}
	} else {
		curvoice.wmeasure = wmeasure
		if (is_voice_sig())
			curvoice.meter = s
		else
			sym_link(s)

		// set the meter of the overlay voices
		for (p_v = curvoice.voice_down; p_v; p_v = p_v.voice_down)
			p_v.wmeasure = wmeasure
	}
}

// link P: or Q:
function link_pq(s, text) {
    var	p_v, s2

	if (curvoice.v == par_sy.top_voice) {
		sym_link(s)
	} else if (voice_tb[par_sy.top_voice].time == s.time) {
		p_v = curvoice
		curvoice = voice_tb[par_sy.top_voice]
		sym_link(s)
		curvoice = p_v
	} else if (voice_tb[par_sy.top_voice].time > s.time) {
		p_v = voice_tb[par_sy.top_voice]
		for (s2 = p_v.sym; ; s2 = s2.next) {
			if (s2.time >= s.time) {
				set_ref(s)
				s.fmt = cfmt
				s.next = s2
				s.prev = s2.prev
				if (s2.prev)
					s.prev.next = s
				else
					p_v.sym = s
				s2.prev = s
				s.v = s2.v
				s.p_v = p_v
				s.st = p_v.st
				break
			}
		}
	} else {
		set_ref(s)
		s.fmt = cfmt
		if (!parse.pq_d)
			parse.pq_d = []
		parse.pq_d.push(s)		// delayed insertion
	}
	if (!parse.pq)
		parse.pq = {}
	parse.pq[text] = s.time
} // link_pq()

/* Q: tempo */
function new_tempo(text) {
    var	i, c, d, nd,
	txt = text,			// (for info.Q)
	s = {
		type: C.TEMPO,
		dur: 0
	}

	// get a note duration
	function get_nd(p) {
	    var	n, d,
		nd = p.match(/(\d+)\/(\d+)/)

		if (nd) {
			d = +nd[2]
			if (d && !isNaN(d) && !(d & (d - 1))) {
				n = +nd[1]
				if (!isNaN(n))
					return C.BLEN * n / d
			}
		}
		syntax(1, "Invalid note duration $1", c)
	} // get_nd()

	set_ref(s)

	if (cfmt.writefields.indexOf('Q') < 0)
		s.invis = true			// don't display

	/* string before */
	if (text[0] == '"') {
		c = text.match(/"([^"]*)"/)		// "
		if (!c) {
			syntax(1, "Unterminated string in Q:")
			return
		}
		s.tempo_str1 = c[1]
		text = text.slice(c[0].length).replace(/^\s+/,'')
	}

	// string after
	if (text.slice(-1) == '"') {
		i = text.indexOf('"')
		s.tempo_str2 = text.slice(i + 1, -1)
		text = text.slice(0, i).replace(/\s+$/,'')
	}

	/* beat */
	i = text.indexOf('=')
	if (i > 0) {
		d = text.slice(0, i).split(/\s+/)
		text = text.slice(i + 1).replace(/^\s+/,'')
		while (1) {
			c = d.shift()
			if (!c)
				break
			nd = get_nd(c)
			if (!nd)
				return
			if (!s.tempo_notes)
				s.tempo_notes = []
			s.tempo_notes.push(nd)
		}

		// tempo value
		if (text.slice(0, 4) == "ca. ") {
			s.tempo_ca = 'ca. '
			text = text.slice(4)
		}
		i = text.indexOf('/')
		if (i > 0) {
			nd = get_nd(text)
			if (!nd)
				return
			s.new_beat = nd
		} else {
			s.tempo = +text
			if (!s.tempo || isNaN(s.tempo)) {
				syntax(1, "Bad tempo value")
				return
			}
		}
	}

	if (parse.state < 2			// if in tune header
	 || (!curvoice.time && !glovar.tempo)) {
		info.Q = txt
		glovar.tempo = s
		return
	}

	if (!glovar.tempo)
		syntax(0, "No previous tempo")
	s.time = curvoice.time
	text = 'Q' + (s.tempo_str1 ? 'S' : '') + s.time
						// accept [Q:"text"][Q:1/4=60]
	if (parse.pq
	 && parse.pq[text] == s.time)
		return				// already seen
	link_pq(s, text)
}

// treat the information fields which may embedded
function do_info(info_type, text) {
    var	s, d1, d2, a, vid, tim, v, p_v

	// skip this line if the current voice is ignored
	// but keep the time related definitions
	if (curvoice && curvoice.ignore) {
		switch (info_type) {
		default:
			return
		case 'P':
		case 'Q':
		case 'V':
			break
		}
	}

	switch (info_type) {

	// info fields in any state
	case 'I':
		self.do_pscom(text)
		break
	case 'L':
		a = text.match(/^1\/(\d+)(=(\d+)\/(\d+))?$/)
		if (a) {
			d1 = +a[1]
			if (!d1 || (d1 & (d1 - 1)) != 0)
				break
			d1 = C.BLEN / d1
			if (a[2]) {		// if '='
				d2 = +a[4]
				d2 = d2 ? +a[3] / d2 * C.BLEN : 0
			} else {
				d2 = d1
			}
		} else if (text == "auto") {
			d1 = d2 = -1
		}
		if (!d2) {
			syntax(1, "Bad L: value")
			break
		}
		if (parse.state <= 1) {
			glovar.ulen = d1
		} else {
			curvoice.ulen = d1;
			curvoice.dur_fact = d2 / d1
		}
		break
	case 'M':
		new_meter(text)
		break
	case 'U':
		set_user(text)
		break

	// fields in tune header or tune body
	case 'P':
		if (!parse.state)
			break
		if (parse.state == 1) {
			info.P = text
			break
		}
		s = {
			type: C.PART,
			text: text,
			time: curvoice.time
		}
	    if (info.P) {
		tim = parse.pq && parse.pq[text] // time of previous P: with same text
		if (tim == s.time)
			break				// already seen
		if (tim != null) {
			syntax(1, "Misplaced P:")	// different dates
			break
		}
	    }

		if (cfmt.writefields.indexOf('P') < 0)
			s.invis = 1 //true
		link_pq(s, text)
		break
	case 'Q':
		if (!parse.state)
			break
		new_tempo(text)
		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
			break
		get_key(text)
		break

	// info in any state
	case 'N':
	case 'R':
		if (!info[info_type])
			info[info_type] = text
		else
			info[info_type] += '\n' + text
		break
	case 'r':
		if (!user.keep_remark
		 || parse.state != 3)
			break
		s = {
			type: C.REMARK,
			text: text,
			dur: 0
		}
		sym_link(s)
		break
	default:
		syntax(0, "'$1:' line ignored", info_type)
		break
	}
}

// music line parsing functions

/* -- adjust the duration and time of symbols in a measure when L:auto -- */
function adjust_dur(s) {
    var	s2, time, auto_time, i, fac;

	/* search the start of the measure */
	s2 = curvoice.last_sym
	if (!s2)
		return;

	/* the bar time is correct if there are multi-rests */
	if (s2.type == C.MREST
	 || s2.type == C.BAR)			/* in second voice */
		return
	while (s2.type != C.BAR && s2.prev)
		s2 = s2.prev;
	time = s2.time;
	auto_time = curvoice.time - time
	fac = curvoice.wmeasure / auto_time

	if (fac == 1)
		return				/* already good duration */

	for ( ; s2; s2 = s2.next) {
		s2.time = time
		if (!s2.dur || s2.grace)
			continue
		s2.dur *= fac;
		s2.dur_orig *= fac;
		time += s2.dur
		if (s2.type != C.NOTE && s2.type != C.REST)
			continue
		for (i = 0; i <= s2.nhd; i++)
			s2.notes[i].dur *= fac
	}
	curvoice.time = s.time = time
}

/* -- parse a bar -- */
function new_bar() {
	var	s2, c, bar_type,
		line = parse.line,
		s = {
			type: C.BAR,
			fname: parse.fname,
			istart: parse.bol + line.index,
			dur: 0,
			multi: 0		// needed for decorations
		}

	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
	}
	bar_type = line.char()
	while (1) {
		c = line.next_char()
		switch (c) {
		case '|':
		case '[':
		case ']':
		case ':':
			bar_type += c
			continue
		}
		break
	}
	if (bar_type[0] == ':') {
		if (bar_type == ':') {		// ":" alone
			bar_type = '|';
			s.bar_dotted = true
		} else {
			s.rbstop = 2		// right repeat with end
		}
	}

	// set the annotations and the decorations
	if (a_gch)
		csan_add(s)
	if (a_dcn.length)
		deco_cnv(s)

	/* if the last element is '[', it may start
	 * a chord or an embedded header */
	if (bar_type.slice(-1) == '['
	 && !(/[0-9" ]/.test(c))) {		// "
		bar_type = bar_type.slice(0, -1);
		line.index--;
		c = '['
	}

	// check if a repeat variant
	if (c > '0' && c <= '9') {
		s.text = c
		while (1) {
			c = line.next_char()
			if ("0123456789,.-".indexOf(c) < 0)
				break
			s.text += c
		}
	} else if (c == '"' && bar_type.slice(-1) == '[') {
		s.text = ""
		while (1) {
			c = line.next_char()
			if (!c) {
				syntax(1, "No end of repeat string")
				return
			}
			if (c == '"') {
				line.index++
				break
			}
			s.text += c
		}
	}

	// ']' as the first character indicates a repeat bar stop
	if (bar_type[0] == ']') {
		s.rbstop = 2			// with end
		if (bar_type.length != 1)
			bar_type = bar_type.slice(1)
		else
			s.invis = true
	}

	s.iend = parse.bol + line.index

	if (s.text
	 && bar_type.slice(-1) == '['
	 && bar_type != '[')
		bar_type = bar_type.slice(0, -1)

	// there cannot be variants on a left repeat bar
	if (bar_type.slice(-1) == ':') {	// left repeat
		s.rbstop = 1			// end the bracket
		if (s.text) {
			syntax(1, "Variant ending on a left repeat bar")
			delete s.text
		}
		curvoice.tie_s_rep = null	// no tie anymore on new variant
	}

	// handle the accidentals (ties and repeat)
	if (s.text) {
		s.rbstart = s.rbstop = 2
		if (s.text[0] == '1') {
			curvoice.tie_s_rep = curvoice.tie_s
			if (curvoice.acc_tie)
				curvoice.acc_tie_rep = curvoice.acc_tie.slice()
			else if (curvoice.acc_tie_rep)
				curvoice.acc_tie_rep = null
		} else {
			curvoice.tie_s = curvoice.tie_s_rep
			if (curvoice.acc_tie_rep)
				curvoice.acc_tie = curvoice.acc_tie_rep.slice()
		}
		if (curvoice.norepbra
		 && !curvoice.second)
			s.norepbra = 1 //true
	}

	if (curvoice.ulen < 0)			// L:auto
		adjust_dur(s);

	// merge ":| |:" into "::" and other cases
	if ((bar_type == "[" || bar_type == "|:")
	 && !curvoice.eoln
	 && !s.a_gch && !s.invis) {		// no annotation nor invisible
		s2 = curvoice.last_sym

		// if the previous symbol is also a bar
		if (s2 && s2.type == C.BAR) {
//		&& !s2.a_gch && !s2.a_dd
//		&& !s.a_gch && !s.a_dd) {

				// remove the invisible variant bars
				// when no shift is needed
				if ((bar_type == "["
				  && !s2.text)
				 || s.norepbra) {
					if (s.text) {
						s2.text = s.text
						if (curvoice.st && !s.norepbra
						 && !(par_sy.staves[curvoice.st - 1]
								.flags & STOP_BAR))
							s2.xsh = 4	// volta shift
					}
//					if (s.a_gch)
//						s2.a_gch = s.a_gch
					if (s.norepbra)
						s2.norepbra = 1 //true
					if (s.rbstart)
						s2.rbstart = s.rbstart
					if (s.rbstop)
						s2.rbstop = s.rbstop
//--fixme: pb when on next line and empty staff above
					return
				}

				// merge back-to-back repeat bars
				if (bar_type == "|:") {
					switch (s2.bar_type) {
					case ":|":		// :| + |: => ::
						s2.bar_type = "::";
						s2.rbstop = 2
						return
					}
				}
		}
	}

	/* set some flags */
	switch (bar_type) {
	case "[":
	case "[]":
	case "[|]":
		s.invis = true;
		bar_type = s.rbstart ? "[" : "[]"
		break
	case ":|:":
	case ":||:":
		bar_type = "::"
		break
	case "||":
		if (cfmt["abc-version"] >= "2.2")
			break
		// fall thru - play repeat on double bar when old ABC version
	case "[|":
	case "|]":
		s.rbstop = 2
		break
	}
	s.bar_type = bar_type
	if (!curvoice.lyric_restart)
		curvoice.lyric_restart = s
	if (!curvoice.sym_restart)
		curvoice.sym_restart = s

	sym_link(s);

	s.st = curvoice.st			/* original staff */

	// possibly shift the volta bracket if not on the first staff
	if (s.text && s.st > 0 && !s.norepbra
	 && !(par_sy.staves[s.st - 1].flags & STOP_BAR)
	 && bar_type != '[')
		s.xsh = 4			// volta shift

	if (!s.bar_dotted && !s.invis)
		curvoice.acc = []		// no accidental anymore
}

// parse %%staves / %%score
// return an array of [vid, flags] / null
function parse_staves(p) {
    var	v, vid,
	vids = {},
		a_vf = [],
		err = false,
		flags = 0,
		brace = 0,
		bracket = 0,
		parenth = 0,
		flags_st = 0,
	e,
	a = p.match(/[^[\]|{}()*+\s]+|[^\s]/g)

	if (!a) {
		syntax(1, errs.bad_val, "%%score")
		return // null
	}
	while (1) {
		e = a.shift()
		if (!e)
			break
		switch (e) {
		case '[':
			if (parenth || brace + bracket >= 2) {
				syntax(1, errs.misplaced, '[');
				err = true
				break
			}
			flags |= brace + bracket == 0 ? OPEN_BRACKET : OPEN_BRACKET2;
			bracket++;
			flags_st <<= 8;
			flags_st |= OPEN_BRACKET
			break
		case '{':
			if (parenth || brace || bracket >= 2) {
				syntax(1, errs.misplaced, '{');
				err = true
				break
			}
			flags |= !bracket ? OPEN_BRACE : OPEN_BRACE2;
			brace++;
			flags_st <<= 8;
			flags_st |= OPEN_BRACE
			break
		case '(':
			if (parenth) {
				syntax(1, errs.misplaced, '(');
				err = true
				break
			}
			flags |= OPEN_PARENTH;
			parenth++;
			flags_st <<= 8;
			flags_st |= OPEN_PARENTH
			break
		case '*':
			if (brace && !parenth && !(flags & (OPEN_BRACE | OPEN_BRACE2)))
				flags |= FL_VOICE
			break
		case '+':
			flags |= MASTER_VOICE
			break
		case ']':
		case '}':
		case ')':
			syntax(1, "Bad voice ID in %%score");
			err = true
			break
		default:	// get / create the voice in the voice table
			vid = e
			while (1) {
				e = a.shift()
				if (!e)
					break
				switch (e) {
				case ']':
					if (!(flags_st & OPEN_BRACKET)) {
						syntax(1, errs.misplaced, ']');
						err = true
						break
					}
					bracket--;
					flags |= brace + bracket == 0 ?
							CLOSE_BRACKET :
							CLOSE_BRACKET2;
					flags_st >>= 8
					continue
				case '}':
					if (!(flags_st & OPEN_BRACE)) {
						syntax(1, errs.misplaced, '}');
						err = true
						break
					}
					brace--;
					flags |= !bracket ?
							CLOSE_BRACE :
							CLOSE_BRACE2;
					flags &= ~FL_VOICE;
					flags_st >>= 8
					continue
				case ')':
					if (!(flags_st & OPEN_PARENTH)) {
						syntax(1, errs.misplaced, ')');
						err = true
						break
					}
					parenth--;
					flags |= CLOSE_PARENTH;
					flags_st >>= 8
					continue
				case '|':
					flags |= STOP_BAR
					continue
				}
				break
			}
			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
		}
	}
	if (flags_st != 0) {
		syntax(1, "'}', ')' or ']' missing in %%score");
		err = true
	}
	if (err || !a_vf.length)
		return //null
	return a_vf
}

// split an info string
function info_split(text) {
	if (!text)
		return []
    var	a = text.match(/[^\s"=]+=?|"[^"]*"/g)	// "
	if (!a) {
//fixme: bad error text
		syntax(1, "Unterminated string")
		return []
	}
	return a
}

// parse a duration and return [numerator, denominator]
// 'line' is not always 'parse.line'
var reg_dur = /(\d*)(\/*)(\d*)/g		/* (stop comment) */

function parse_dur(line) {
	var res, num, den;

	reg_dur.lastIndex = line.index;
	res = reg_dur.exec(line.buffer)
	if (!res[0])
		return [1, 1];
	num = res[1] || 1;
	den = res[3] || 1
	if (!res[3])
		den *= 1 << res[2].length;
	line.index = reg_dur.lastIndex
	return [+num, +den]
}

// parse the note accidental and pitch
function parse_acc_pit(line) {
    var	note, acc, pit, d, nd,
	c = line.char()

	// optional accidental
	switch (c) {
	case '^':
		c = line.next_char()
		if (c == '^') {
			acc = 2;
			c = line.next_char()
		} else {
			acc = 1
		}
		break
	case '=':
		acc = 3;
		c = line.next_char()
		break
	case '_':
		c = line.next_char()
		if (c == '_') {
			acc = -2;
			c = line.next_char()
		} else {
			acc = -1
		}
		break
	}

	/* look for microtone value */
	if (acc == 1 || acc == -1) {
	    if ((c >= '1' && c <= '9')
	     || c == '/') {			// shortcut
		nd = parse_dur(line);
		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()
	if (pit < 16) {
		syntax(1, "'$1' is not a note", line.buffer[line.index - 1])
		return //undefined
	}

	// octave
	while (c == "'") {
		pit += 7;
		c = line.next_char()
	}
	while (c == ',') {
		pit -= 7;
		c = line.next_char()
	}
	note = {
		pit: pit,
		shhd: 0,
		shac: 0
	}
	if (acc)
		note.acc = acc
	return 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 = ABC note
// - octave = 'o' + ABC note in C..B interval
// - key    = 'k' + scale index
// - tonic  = 't' + mode index
// - any    = '*'
// The 'map' is stored in the note. It is an array of
//	[0] array of heads (glyph names)
//	[1] print (note)
//	[2] color
//	[3] play (note)
function set_map(p_v, note, acc,
		trp_p) {			// flag "do transpose?"
    var	nn = not2abc(note.pit, acc),
	map = maps[p_v.map]

	if (!map)
		return

	// test if 'nn' is in the map
	function map_p() {
		if (map[nn])
			return 1 //true
	    var	sf, d

		nn = 'o' + nn.replace(/[',]+/, '')	// '
		if (map[nn])
			return 1 //true
//fixme: useless
		d = abc2svg.keys[p_v.ckey.k_sf + 7][(note.pit + 75) % 7]
		d = (!d && acc == 3) ? 0 : acc
		nn = 'k' + ['__','_','','^','^^','='][d + 2]	// key chromatic
			+ ntb[(note.pit + 75 - p_v.ckey.k_sf * 11) % 7]
		if (map[nn])
			return 1 //true
		nn = nn.replace(/[_=^]/g,'')		// key diatonic
		if (map[nn])
			return 1 //true
		sf = p_v.ckey.k_sf + [0, 2, 4, -1, 1, 3, -2][p_v.ckey.k_mode]
		if (sf < -7)
			sf += 7
		else if (sf > 7)
			sf -= 7
		d = abc2svg.keys[sf + 7]
				[(note.pit + 75) % 7]
		if (d && acc == 3)
			d = -d
		else if (!d && !acc)
			d = 3
		else
			d = acc - d
		nn = 't' + ['__','_','=','^','^^','']	// tonic chromatic
				[d + 2]
			+ ntb[(note.pit + 75 - p_v.ckey.k_mode
				 - p_v.ckey.k_sf * 11) % 7]
		if (map[nn])
			return 1 //true
		nn = nn.replace(/[_=^]/g,'')		// tonic diatonic
		if (map[nn])
			return 1 //true
		nn = '*'				// any note
		return map[nn]
	} // map_p()

	if (!map_p())					// note in the map?
		return					// no
	map = map[nn]

	if (trp_p) {
		if (map[1] && map[1].notrp)
			note.notrp = 1 //true	// no transpose
		return
	}

	if (map[1]				// if note transpose
	 && !note.map) {			// for the first time
			note.pit = map[1].pit
			note.acc = map[1].acc
			if (map[1].notrp) {
				note.notrp = 1 //true	// no transpose
				note.noplay = 1 //true	// no play
			}
	}
	note.map = map

	if (map[2])				// if color
		note.color = map[2]
	nn = map[3]
	if (nn)					// if play map
		note.midi = pit2mid(nn.pit + 19, nn.acc)
}

/* -- parse note or rest with pitch and length -- */
// 'line' is not always 'parse.line'
function parse_basic_note(line, ulen) {
	var	nd,
		note = parse_acc_pit(line)

	if (!note)
		return //null

	// duration
	if (line.char() == '0') {		// compatibility
		parse.stemless = true;
		line.index++
	}
	nd = parse_dur(line);
	note.dur = ulen * nd[0] / nd[1]
	return note
}

function parse_vpos() {
	var	line = parse.line,
		ty = 0

	if (a_dcn.length && a_dcn[a_dcn.length - 1] == "dot") {
		ty = C.SL_DOTTED
		a_dcn.pop()
	}
	switch (line.next_char()) {
	case "'":
		line.index++
		return ty + C.SL_ABOVE
	case ",":
		line.index++
		return ty + C.SL_BELOW
	case '?':				// slur between staves (like ~)
		line.index++
		return ty + C.SL_CENTER
	}
	return ty + C.SL_AUTO
}

// on end of slur, create the slur
function slur_add(s, nt) {		// nt = note if slur ending on note
    var	i, s2, sl

	// go back and find the last start of slur
	for (i = curvoice.sls.length; --i >= 0; ) {
		sl = curvoice.sls[i]

		// the slur must not start and stop on a same symbol
		if (sl.ss == s)
			continue
		curvoice.sls.splice(i, 1)
		sl.se = s			// ending symbol
		if (nt)
			sl.nte = nt
		s2 = sl.ss			// start of slur
		if (!s2.sls)
			s2.sls = []
		s2.sls.push(sl)

		// set a flag if the slur starts on a grace note
		if (sl.grace)
			sl.grace.sl1 = true
		return
	}

	// the lack of a starting slur may be due to a repeat
	for (s2 = s.prev; s2; s2 = s2.prev) {
		if (s2.type == C.BAR
		 && s2.bar_type[0] == ':'
		 && s2.text) {
			if (!s2.sls)
				s2.sls = [];
			s2.sls.push({
//fixme: should go back to the bar "|1" and find the slur type...
				ty: C.SL_AUTO,
				ss: s2,
				se: s
			})
			if (nt)
				s2.sls[s2.sls.length - 1].nte = nt
			return
		}
	}
//	syntax(1, "End of slur without start")
	if (!s.sls)
		s.sls = [];
	s.sls.push({
		ty: C.SL_AUTO,
		se: s,
		loc: 'i'			// no slur start
	})
	if (nt)
		s.sls[s.sls.length - 1].nte = nt
}

// 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 == "object") {
			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
		p0 = cfmt.temper[abc2svg.p_b40[pit % 7]]	// main note
		if (typeof acc != "object") {	// if not a fraction
			b40 = abc2svg.p_b40[pit % 7] + acc
			p1 = cfmt.temper[b40]
			if (s > 0) {			// sharp
				if (p1 < p0)
					p1 += 12
			} else {
				if (p1 > p0)
					p1 -= 12
			}
			return p1 + o
		}

		if (acc[1] == cfmt.nedo) {	// fraction with 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()

// handle the ties
// @s = tie ending smbol
// @tei_s = tie starting symbol
function do_ties(s, tie_s) {
    var	i, m, not1, not2, mid, g,
	nt = 0,
	se = (tie_s.time + tie_s.dur) == curvoice.time	// 'start-end' flag

	for (m = 0; m <= s.nhd; m++) {
		not2 = s.notes[m]
		mid = not2.midi
		if (tie_s.type != C.GRACE) {
			for (i = 0; i <= tie_s.nhd; i++) {
				not1 = tie_s.notes[i]
				if (!not1.tie_ty)
					continue
				if (not1.midi == mid
				 && (!se
				  || !not1.tie_e)) {	// (if unison)
					not2.tie_s = not1
					not2.s = s
					if (se) {
						not1.tie_e = not2
						not1.s = tie_s
					}
					nt++
					break
				}
			}
		} else {
			for (g = tie_s.extra; g; g = g.next) {
				not1 = g.notes[0]	// (fixme: only one note)
				if (!not1.tie_ty)
					continue
				if (not1.midi == mid) {
					g.ti1 = true
					not2.tie_s = not1
					not2.s = s
					not1.tie_e = not2
					not1.s = g
					nt++
					break
				}
			}
		}
	}

	if (!nt)
		error(1, tie_s, "Bad tie")
	else
		s.ti2 = true
} // do_ties()

// (possible hook)
Abc.prototype.new_note = function(grace, sls) {
    var	note, s, in_chord, c, tie_s, acc_tie,
	i, n, s2, nd, res, num, apit, div, ty,
	chdur = 1,
	dpit = 0,
	sl1 = [],
	line = parse.line,
	a_dcn_sav = a_dcn		// save parsed decoration names

	a_dcn = []
	parse.stemless = false;
	s = {
		type: C.NOTE,
		fname: parse.fname,
		stem: 0,
		multi: 0,
		nhd: 0,
		xmx: 0
	}
	s.istart = parse.bol + line.index

	if (curvoice.color)
		s.color = curvoice.color

	if (grace) {
		s.grace = true
	} else {
		if (curvoice.tie_s) {	// if tie from previous note / grace note
			tie_s = curvoice.tie_s
			curvoice.tie_s = null
		}
		if (a_gch)
			csan_add(s)
		if (parse.repeat_n) {
			s.repeat_n = parse.repeat_n;
			s.repeat_k = parse.repeat_k;
			parse.repeat_n = 0
		}
	}
	c = line.char()
	switch (c) {
	case 'X':
		s.invis = true
	case 'Z':
		s.type = C.MREST;
		c = line.next_char()
		s.nmes = (c > '0' && c <= '9') ? line.get_int() : 1;
		if (curvoice.wmeasure == 1) {
			error(1, s, "multi-measure rest, but no measure!")
			return
		}
		s.dur = curvoice.wmeasure * s.nmes

		// convert 'Z'/'Z1' to a whole measure rest
		if (s.nmes == 1) {
			s.type = C.REST;
			s.dur_orig = s.dur;
			s.fmr = 1		// full measure rest
			s.notes = [{
				pit: 18,
				dur: s.dur
			}]
		} else {
			glovar.mrest_p = true
			if (par_sy.voices.length == 1) {
				s.tacet = curvoice.tacet
				delete s.invis	// show the 'H' when 'Xn'
			}
		}
		break
	case 'y':
		s.type = C.SPACE;
		s.invis = true;
		s.dur = 0;
		c = line.next_char()
		if (c >= '0' && c <= '9')
			s.width = line.get_int()
		else
			s.width = 10
		if (tie_s) {
			curvoice.tie_s = tie_s
			tie_s = null
		}
		break
	case 'x':
		s.invis = true
	case 'z':
		s.type = C.REST;
		line.index++;
		nd = parse_dur(line);
		s.dur_orig = ((curvoice.ulen < 0) ?
					C.BLEN :
					curvoice.ulen) * nd[0] / nd[1];
		if (s.dur_orig < 12) {
			error(0, s, "Bad note duration $1", s.dur_orig)
			s.dur_orig = 12
		}
		s.dur = s.dur_orig * curvoice.dur_fact;
		if (s.dur == curvoice.wmeasure)
			s.fmr = 1		// full measure rest
		s.notes = [{
			pit: 18,
			dur: s.dur_orig
		}]
		break
	case '[':			// chord
		in_chord = true;
		c = line.next_char()
		i = line.buffer.indexOf(']', line.index)
		if (i < 0) {
			syntax(1, "No end of chord")
			return
		}
		n = line.index			// save the parser index
		line.index = i + 1		// set the parser to the end of chord
		nd = parse_dur(line)
		chdur = nd[0] / nd[1]		// length factor of the chord
		in_chord = reg_dur.lastIndex	// hack: index after the chord length
		line.index = n			// restore the parser index
		// fall thru
	default:			// accidental, chord, note
		if (curvoice.acc_tie) {
			acc_tie = curvoice.acc_tie
			curvoice.acc_tie = null
		}
		s.notes = []

		// loop on the chord
		while (1) {

			// when in chord, get the slurs and decorations
			if (in_chord) {
				while (1) {
					if (!c)
						break
					i = c.charCodeAt(0);
					if (i >= 128) {
						syntax(1, errs.not_ascii)
						return //null
					}
					ty = char_tb[i]
					switch (ty[0]) {
					case '(':
						sl1.push(parse_vpos());
						c = line.char()
						continue
					case '!':
						if (ty.length > 1)
							a_dcn.push(ty.slice(1, -1))
						else
							get_deco()	// line -> a_dcn
						c = line.next_char()
						continue
					}
					break
				}
			}
			note = parse_basic_note(line,
					s.grace ? C.BLEN / 4 :
					curvoice.ulen < 0 ?
						C.BLEN :
						curvoice.ulen)
			if (!note)
				return //null

			note.dur *= chdur		// chord factor
			if (note.dur < 12) {
				error(0, s, "Bad note duration $1", note.dur)
				note.dur = 12
			}

			if (curvoice.octave)
				note.pit += curvoice.octave * 7

			// get the real accidental
			apit = note.pit + 19		// pitch from C-1
			i = note.acc
			if (!i) {
				if (cfmt["propagate-accidentals"][0] == 'p')
					i = curvoice.acc[apit % 7]
				else
					i = curvoice.acc[apit]
				if (!i)
					i = curvoice.ckey.k_map[apit % 7] || 0
			}

			if (i
			 && !curvoice.ckey.k_drum) {
				if (cfmt["propagate-accidentals"][0] == 'p')
					curvoice.acc[apit % 7] = i
				else if (cfmt["propagate-accidentals"][0] != 'n')
					curvoice.acc[apit] = i
			}

			if (acc_tie && acc_tie[apit])
				i = acc_tie[apit]	// tied note

			// set the MIDI pitch
			if (!note.midi)		// if not map play
				note.midi = pit2mid(apit, i)

			// transpose
			if (curvoice.tr_sco) {
				set_map(curvoice, note, i, 1)	// possible transpose?
			    if (!note.notrp) {			// yes
				i = nt_trans(note, i)
				if (i == -3) {		// if triple sharp/flat
					error(1, s, "triple sharp/flat")
					i = note.acc > 0 ? 1 : -1
					note.pit += i
					note.acc = i
				}
				dpit = note.pit + 19 - apit
			    }
			}
			if (curvoice.tr_snd)
				note.midi += curvoice.tr_snd
			if (curvoice.map)
				set_map(curvoice, note, i)

//fixme: does not work if transposition
			if (i) {
				switch (cfmt["writeout-accidentals"][1]) {
				case 'd':			// added
					s2 = curvoice.ckey
					if (!s2.k_a_acc)
						break
					for (n = 0; n < s2.k_a_acc.length; n++) {
						if ((s2.k_a_acc[n].pit - note.pit)
								% 7 == 0) {
							note.acc = i
							break
						}
					}
					break
				case 'l':			// all
					note.acc = i
					break
				}
			}

			// starting slurs
			if (sl1.length) {
				while (1) {
					i = sl1.shift()
					if (!i)
						break
					curvoice.sls.push({
						ty: i,
						ss: s,
						nts: note	// starting note
					})
				}
			}
			s.notes.push(note)
			if (!in_chord)
				break

			// in chord: get the ending slurs and the ties
			c = line.char()
			while (1) {
				switch (c) {
				case ')':
					slur_add(s, note)
					c = line.next_char()
					continue
				case '-':
					note.tie_ty = parse_vpos()
					note.s = s
					curvoice.tie_s = s
					s.ti1 = true
					if (curvoice.acc[apit]
					 || (acc_tie
					  && acc_tie[apit])) {
						if (!curvoice.acc_tie)
							curvoice.acc_tie = []
						i = curvoice.acc[apit]
						if (acc_tie && acc_tie[apit])
							i = acc_tie[apit]
						curvoice.acc_tie[apit] = i
					}
					c = line.char()
					continue
				case '.':
					c = line.next_char()
					switch (c) {
					case '-':
					case '(':
						a_dcn.push("dot")
						continue
					}
					syntax(1, "Misplaced dot")
					break
				}
				break
			}
			if (a_dcn.length) {
				s.time = curvoice.time	// (needed for !tie)!
				dh_cnv(s, note)
			}

			if (c == ']') {
				line.index = in_chord
				s.nhd = s.notes.length - 1
				break
			}
		}

		// handle the starting slurs
		if (sls.length) {
			while (1) {
				i = sls.shift()
				if (!i)
					break
				curvoice.sls.push({
					ty: i,
					ss: s
					// no starting note
				})
				if (grace)
					curvoice.sls[curvoice.sls.length - 1].grace =
										grace
			}
		}

		// the duration of the chord is the duration of the 1st note
		s.dur_orig = s.notes[0].dur;
		s.dur = s.notes[0].dur * curvoice.dur_fact
		break
	}
	if (s.grace && s.type != C.NOTE) {
		syntax(1, errs.bad_grace)
		return //null
	}

	if (s.notes) {				// if note or rest
		if (!s.fmr) {			// (but not full measure rest)
			n = s.dur_orig / 12	// check its duration
			i = 0
			while (!(n & 1)) {
				n >>= 1
				i++
			}
			if ((n + 1) & n)
				error(0, s, "Non standard note duration $1",
					n + '/' + (1 << (6 - i)))
		}
		if (!grace) {
			switch (curvoice.pos.stm & 0x07) {
			case C.SL_ABOVE: s.stem = 1; break
			case C.SL_BELOW: s.stem = -1; break
			case C.SL_HIDDEN: s.stemless = true; break
			}

			// adjust the symbol duration
			num = curvoice.brk_rhythm
			if (num) {
				curvoice.brk_rhythm = 0;
				s2 = curvoice.last_note
				if (num > 0) {
					n = num * 2 - 1;
					s.dur = s.dur * n / num;
					s.dur_orig = s.dur_orig * n / num
					for (i = 0; i <= s.nhd; i++)
						s.notes[i].dur =
							s.notes[i].dur * n / num;
					s2.dur /= num;
					s2.dur_orig /= num
					for (i = 0; i <= s2.nhd; i++)
						s2.notes[i].dur /= num
				} else {
					num = -num;
					n = num * 2 - 1;
					s.dur /= num;
					s.dur_orig /= num
					for (i = 0; i <= s.nhd; i++)
						s.notes[i].dur /= num;
					s2.dur = s2.dur * n / num;
					s2.dur_orig = s2.dur_orig * n / num
					for (i = 0; i <= s2.nhd; i++)
						s2.notes[i].dur =
							s2.notes[i].dur * n / num
				}
				curvoice.time = s2.time + s2.dur;

				// adjust the time of the grace notes, bars...
				for (s2 = s2.next; s2; s2 = s2.next)
					s2.time = curvoice.time
			}
		} else {		/* grace note - adjust its duration */
			div = curvoice.ckey.k_bagpipe ? 8 : 4
			for (i = 0; i <= s.nhd; i++)
				s.notes[i].dur /= div;
			s.dur /= div;
			s.dur_orig /= div
			if (grace.stem)
				s.stem = grace.stem
		}

		curvoice.last_note = s

		// get the possible ties and end of slurs
		c = line.char()
		while (1) {
			switch (c) {
			case '.':
				if (line.buffer[line.index + 1] != '-')
					break
				a_dcn.push("dot")
				line.index++
				// fall thru
			case '-':
				ty = parse_vpos()
				for (i = 0; i <= s.nhd; i++) {
					s.notes[i].tie_ty = ty
					s.notes[i].s = s
				}
				curvoice.tie_s = grace || s
				curvoice.tie_s.ti1 = true
				for (i = 0; i <= s.nhd; i++) {
					note = s.notes[i]
					apit = note.pit + 19	// pitch from C-1
						- dpit		// (if transposition)
					if (curvoice.acc[apit]
					 || (acc_tie
					  && acc_tie[apit])) {
						if (!curvoice.acc_tie)
							curvoice.acc_tie = []
						n = curvoice.acc[apit]
						if (acc_tie && acc_tie[apit])
							n = acc_tie[apit]
						curvoice.acc_tie[apit] = n
					}
				}
				c = line.char()
				continue
			}
			break
		}

		// handle the ties ending on this chord/note
		if (tie_s)		// if tie from previous note / grace note
			do_ties(s, tie_s)
	}

	sym_link(s)

	if (!grace) {
		if (!curvoice.lyric_restart)
			curvoice.lyric_restart = s
		if (!curvoice.sym_restart)
			curvoice.sym_restart = s
	}

	if (a_dcn_sav.length) {
		a_dcn = a_dcn_sav
		deco_cnv(s, s.prev)
	}
	if (grace && s.ottava)
		grace.ottava = s.ottava
	if (parse.stemless)
		s.stemless = true
	s.iend = parse.bol + line.index
	return s
}

// adjust the duration of the elements in a tuplet
function tp_adj(s, fact) {
    var	d,
	tim = s.time,
	to = curvoice.time - tim,	// previous delta time
	tt = to * fact			// new delta time

	curvoice.time = tim + tt
	while (1) {
//fixme: tuplets in grace notes?
		s.in_tuplet = true
		if (!s.grace) {
			s.time = tim
			if (s.dur) {
				d = Math.round(s.dur * tt / to)	// new duration
				to -= s.dur		// old remaining time
				s.dur = d
				tt -= s.dur		// new remaining time
				tim += s.dur
			}
		}
		if (!s.next) {
			if (s.tpe)
				s.tpe++
			else
				s.tpe = 1
			break
		}
		s = s.next
	}
} // tp_adj()

// get a decoration
function get_deco() {
    var	c,
	line = parse.line,
	i = line.index,		// in case no deco end
	dcn = ""

	while (1) {
		c = line.next_char()
		if (!c) {
			line.index = i
			syntax(1, "No end of decoration")
			return
		}
		if (c == '!')
			break
		dcn += c
	}
	a_dcn.push(dcn)
} // get_deco()

// characters in the music line (ASCII only)
var nil = "0",
    char_tb = [
	nil, nil, nil, nil,		/* 00 - .. */
	nil, nil, nil, nil,
	nil, " ", "\n", nil,		/* . \t \n . */
	nil, nil, nil, nil,
	nil, nil, nil, nil,
	nil, nil, nil, nil,
	nil, nil, nil, nil,
	nil, nil, nil, nil,		/* .. - 1f */
	" ", "!", '"', "i",		/* (sp) ! " # */
	"\n", nil, "&", nil,		/* $ % & ' */
	"(", ")", "i", nil,		/* ( ) * + */
	nil, "-", "!dot!", nil,		/* , - . / */
	nil, nil, nil, nil, 		/* 0 1 2 3 */
	nil, nil, nil, nil, 		/* 4 5 6 7 */
	nil, nil, "|", "i",		/* 8 9 : ; */
	"<", "n", "<", "i",		/* < = > ? */
	"i", "n", "n", "n",		/* @ A B C */
	"n", "n", "n", "n", 		/* D E F G */
	"!fermata!", "d", "d", "d",	/* H I J K */
	"!emphasis!", "!lowermordent!",
		"d", "!coda!",		/* L M N O */
	"!uppermordent!", "d",
		"d", "!segno!",		/* P Q R S */
	"!trill!", "d", "d", "d",	/* T U V W */
	"n", "d", "n", "[",		/* X Y Z [ */
	"\\","|", "n", "n",		/* \ ] ^ _ */
	"i", "n", "n", "n",	 	/* ` a b c */
	"n", "n", "n", "n",	 	/* d e f g */
	"d", "d", "d", "d",		/* h i j k */
	"d", "d", "d", "d",		/* l m n o */
	"d", "d", "d", "d",		/* p q r s */
	"d", "!upbow!",
		"!downbow!", "d",	/* t u v w */
	"n", "n", "n", "{",		/* x y z { */
	"|", "}", "!gmark!", nil,	/* | } ~ (del) */
] // char_tb[]

function parse_music_line() {
	var	grace, last_note_sav, a_dcn_sav, no_eol, s, tps,
		tp = [],
		tpn = -1,
		sls = [],
		line = parse.line

	// check if a transposing macro matches a source sequence
	// if yes return the base note
	function check_mac(m) {
	    var	i, j, b

		for (i = 1, j = line.index + 1; i < m.length; i++, j++) {
			if (m[i] == line.buffer[j])
				continue
			if (m[i] != 'n')		// search the base note
				return //undefined
			b = ntb.indexOf(line.buffer[j])
			if (b < 0)
				return //undefined
			while (line.buffer[j + 1] == "'") {
				b += 7;
				j++
			}
			while (line.buffer[j + 1] == ',') {
				b -= 7;
				j++
			}
		}
		line.index = j
		return b
	} // check_mac()

	// convert a note as a number into a note as a ABC string
	function n2n(n) {
	    var	c = ''

		while (n < 0) {
			n += 7;
			c += ','
		}
		while (n >= 14) {
			n -= 7;
			c += "'"
		}
		return ntb[n] + c
	} // n2n()

	// expand a transposing macro
	function expand(m, b) {
		if (b == undefined)		// if static macro
			return m
	    var	c, i,
		r = "",				// result
		n = m.length

		for (i = 0; i < n; i++) {
			c = m[i]
			if (c >= 'h' && c <= 'z') {
				r += n2n(b + c.charCodeAt(0) - 'n'.charCodeAt(0))
			} else {
				r += c
			}
		}
		return r
	} // expand()

	// parse a macro
	function parse_mac(k, m, b) {
	    var	te, ti, curv, s,
		line_sav = line,
		istart_sav = parse.istart;

		parse.line = line = new scanBuf;
		parse.istart += line_sav.index;

		// if the macro is not displayed
		if (cfmt.writefields.indexOf('m') < 0) {

			// build the display sequence from the original sequence
			line.buffer = k.replace('n', n2n(b))
			s = curvoice.last_sym
			ti = curvoice.time		// start time
			parse_seq(true)
			if (!s)
				s = curvoice.sym
			for (s = s.next ; s; s = s.next)
				s.noplay = true
			te = curvoice.time		// end time
			curv = curvoice

			// and put the macro sequence in a play specific voice
			curvoice = clone_voice(curv.id + '-p')
			if (!par_sy.voices[curvoice.v]) {
				curvoice.second = true
				par_sy.voices[curvoice.v] = {
					st: curv.st,
					second: true,
					range: curvoice.v
				}
			}
			curvoice.time = ti
			s = curvoice.last_sym
			parse.line = line = new scanBuf
			parse.istart += line_sav.index
			line.buffer = expand(m, b)
			parse_seq(true)
			if (curvoice.time != te)
				syntax(1, "Bad length of the macro sequence")
			if (!s)
				s = curvoice.sym
			for ( ; s; s = s.next)
				s.invis = s.play = true
			curvoice = curv
		} else {
			line.buffer = expand(m, b)
			parse_seq(true)
		}

		parse.line = line = line_sav
		parse.istart = istart_sav
	} // parse_mac()

	// parse a music sequence
	function parse_seq(in_mac) {
	    var	c, idx, type, k, s, dcn, i, n, text, note

		while (1) {
			c = line.char()
			if (!c)
				break

			// check if start of a macro
			if (!in_mac && maci[c]) {
				n = undefined
				for (k in mac) {
					if (!mac.hasOwnProperty(k)
					 || k[0] != c)
						continue
					if (k.indexOf('n') < 0) {
						if (line.buffer.indexOf(k, line.index)
								!= line.index)
							continue
						line.index += k.length
					} else {
						n = check_mac(k)
						if (n == undefined)
							continue
					}
					parse_mac(k, mac[k], n)
					n = 1
					break
				}
				if (n)
					continue
			}

			idx = c.charCodeAt(0)
			if (idx >= 128) {
				syntax(1, errs.not_ascii)
				line.index++
				break
			}

			type = char_tb[idx]
			switch (type[0]) {
			case ' ':			// beam break
				s = curvoice.last_note
				if (s) {
					s.beam_end = true
					if (grace)
						grace.gr_shift = true
				}
				break
			case '\n':			// line break
				if (cfmt.barsperstaff)
					break
				curvoice.eoln = true
				break
			case '&':			// voice overlay
				if (grace) {
					syntax(1, errs.bad_grace)
					break
				}
				c = line.next_char()
				if (c == ')') {
					get_vover(c)	// full overlay stop
					break
				}
				get_vover('&')
				continue
			case '(':			// slur start - tuplet - vover
				c = line.next_char()
				if (c > '0' && c <= '9') {	// tuplet
					if (grace) {
						syntax(1, errs.bad_grace)
						break
					}
				    var	pplet = line.get_int(),
					qplet = qplet_tb[pplet],
					rplet = pplet

					c = line.char()
					if (c == ':') {
						c = line.next_char()
						if (c > '0' && c <= '9') {
							qplet = line.get_int();
							c = line.char()
						}
						if (c == ':') {
							c = line.next_char()
							if (c > '0' && c <= '9') {
								rplet = line.get_int();
								c = line.char()
							} else {
								syntax(1, "Invalid 'r' in tuplet")
								continue
							}
						}
					}
					if (qplet == 0 || qplet == undefined)
						qplet = (curvoice.wmeasure % 9) == 0 ?
									3 : 2;
					if (tpn < 0)
						tpn = tp.length	// new tuplet
					tp.push({
						p: pplet,
						q: qplet,
						r: rplet,
						ro: rplet,
						f: curvoice.tup || cfmt.tuplets
					})
					continue
				}
				if (c == '&') {		// voice overlay start
					if (grace) {
						syntax(1, errs.bad_grace)
						break
					}
					get_vover('(')
					break
				}
				line.index--;
				sls.push(parse_vpos())
				continue
			case ')':			// slur end
				s = curvoice.last_sym
				if (s) {
					switch (s.type) {
					case C.SPACE:
						if (!s.notes) {
							s.notes = []
							s.notes[0] = {}
						}
					case C.NOTE:
					case C.REST:
						break
					case C.GRACE:

						// stop the slur on the last grace note
						for (s = s.extra; s.next; s = s.next)
							;
						break
					default:
						s = null
						break
					}
				}
				if (!s) {
					syntax(1, errs.bad_char, c)
					break
				}
				slur_add(s)
				break
			case '!':			// start of decoration
				if (type.length > 1)	// decoration letter
					a_dcn.push(type.slice(1, -1))
				else
					get_deco()	// (line -> a_dcn)
				break
			case '"':
				if (grace) {
					syntax(1, errs.bad_grace)
					break
				}
				parse_gchord(type)
				break
			case '[':
				if (type.length > 1) {	// U: [I:xxx]
					self.do_pscom(type.slice(3, -1))
					break
				}
			    var c_next = line.buffer[line.index + 1]

				if ('|[]: "'.indexOf(c_next) >= 0
				 || (c_next >= '1' && c_next <= '9')) {
					if (grace) {
						syntax(1, errs.bar_grace)
						break
					}
					new_bar()
					continue
				}
				if (line.buffer[line.index + 2] == ':') {
					if (grace) {
						syntax(1, errs.bad_grace)
						break
					}
					i = line.buffer.indexOf(']', line.index + 1)
					if (i < 0) {
						syntax(1, "Lack of ']'")
						break
					}
					text = line.buffer.slice(line.index + 3, i).trim()

					parse.istart = parse.bol + line.index;
					parse.iend = parse.bol + ++i;
					line.index = 0;
					do_info(c_next, text);
					line.index = i
					continue
				}
				// fall thru ('[' is start of chord)
			case 'n':				// note/rest
				s = self.new_note(grace, sls)
				if (!s)
					continue

				// handle the tuplets
				if (grace || !s.notes)
					continue

				if (tpn >= 0) {		// new tuplet
					s.tp = tp.slice(tpn)
					tpn = -1
					if (tps)
						s.tp[0].s = tps	// if nested
					tps = s
				} else if (!tps) {
					continue	// no tuplet active
				}

				k = tp[tp.length - 1]
				if (--k.r > 0)
					continue	// not end of tuplet yet

				while (1) {
					tp_adj(tps, k.q / k.p)
					i = k.ro	// number of notes of this tuplet
					if (k.s)
						tps = k.s  // start of upper tuplet

					tp.pop()		// previous level
					if (!tp.length) {
						tps = null	// done
						break
					}
					k = tp[tp.length - 1]
					k.r -= i
					if (k.r > 0)
						break
				}
				continue
			case '<':				/* '<' and '>' */
				if (!curvoice.last_note) {
					syntax(1, "No note before '<'")
					break
				}
				if (grace) {
					syntax(1, "Cannot have a broken rhythm in grace notes")
					break
				}
				n = c == '<' ? 1 : -1
				while (c == '<' || c == '>') {
					n *= 2;
					c = line.next_char()
				}
				curvoice.brk_rhythm = n
				continue
			case 'i':				// ignore
				break
			case '{':
				if (grace) {
					syntax(1, "'{' in grace note")
					break
				}
				last_note_sav = curvoice.last_note;
				curvoice.last_note = null;
				a_dcn_sav = a_dcn;
				a_dcn = []
				grace = {
					type: C.GRACE,
					fname: parse.fname,
					istart: parse.bol + line.index,
					dur: 0,
					multi: 0
				}
				if (curvoice.color)
					grace.color = curvoice.color
				switch (curvoice.pos.gst & 0x07) {
				case C.SL_ABOVE: grace.stem = 1; break
				case C.SL_BELOW: grace.stem = -1; break
				case C.SL_HIDDEN: grace.stem = 2; break	/* opposite */
				}
				sym_link(grace);
				c = line.next_char()
				if (c == '/') {
					grace.sappo = true	// acciaccatura
					break
				}
				continue
			case '|':
				if (grace) {
					syntax(1, errs.bar_grace)
					break
				}
				new_bar()
				continue
			case '}':
				if (curvoice.ignore) {
					grace = null
					break
				}
				s = curvoice.last_note
				if (!grace || !s) {
					syntax(1, errs.bad_char, c)
					break
				}
				if (a_dcn.length)
					syntax(1, "Decoration ignored");
				grace.extra = grace.next;
				grace.extra.prev = null;
				grace.next = null;
				curvoice.last_sym = grace;
				grace = null
				if (!s.prev			// if one grace note
				 && !curvoice.ckey.k_bagpipe) {
					for (i = 0; i <= s.nhd; i++)
						s.notes[i].dur *= 2;
					s.dur *= 2;
					s.dur_orig *= 2
				}
				curvoice.last_note = last_note_sav;
				a_dcn = a_dcn_sav
				break
			case "\\":
				if (!line.buffer[line.index + 1]) {
					no_eol = true
					break
				}
				// fall thru
			default:
				syntax(1, errs.bad_char, c)
				break
			}
			line.index++
		}
	} // parse_seq()

	if (parse.state != 3)		// if not in tune body
		return

	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)
			grace.prev.next = null
	}
	if (!no_eol && !cfmt.barsperstaff && !vover
	 && char_tb['\n'.charCodeAt(0)] == '\n')
		curvoice.eoln = true
	if (curvoice.eoln && cfmt.breakoneoln && curvoice.last_note)
		curvoice.last_note.beam_end = true
}
// abc2svg - subs.js - text output
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// add font styles
    var	sheet
var add_fstyle = abc2svg.el
    ? function(s) {
    var	e

	font_style += "\n" + s
	if (!abc2svg.styles) {
		e = document.createElement('style')
		document.head.appendChild(e)
		abc2svg.styles = e
	}
	sheet = abc2svg.styles.sheet
	s = s.match(/[^{]+{[^}]+}/g)	// insert each style
	while (1) {
		e = s.shift()
		if (!e)
			break
		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
    sw_tb = new Float32Array([
	.000,.000,.000,.000,.000,.000,.000,.000,	// 00
	.000,.000,.000,.000,.000,.000,.000,.000,
	.000,.000,.000,.000,.000,.000,.000,.000,	// 10
	.000,.000,.000,.000,.000,.000,.000,.000,
	.250,.333,.408,.500,.500,.833,.778,.333,	// 20
	.333,.333,.500,.564,.250,.564,.250,.278,
	.500,.500,.500,.500,.500,.500,.500,.500,	// 30
	.500,.500,.278,.278,.564,.564,.564,.444,
	.921,.722,.667,.667,.722,.611,.556,.722,	// 40
	.722,.333,.389,.722,.611,.889,.722,.722,
	.556,.722,.667,.556,.611,.722,.722,.944,	// 50
	.722,.722,.611,.333,.278,.333,.469,.500,
	.333,.444,.500,.444,.500,.444,.333,.500,	// 60
	.500,.278,.278,.500,.278,.778,.500,.500,
	.500,.500,.333,.389,.278,.500,.500,.722,	// 70
	.500,.500,.444,.480,.200,.480,.541,.500]),
// sans-serif
    ssw_tb = new Float32Array([
	.000,.000,.000,.000,.000,.000,.000,.000,	// 00
	.000,.000,.000,.000,.000,.000,.000,.000,
	.000,.000,.000,.000,.000,.000,.000,.000,	// 10
	.000,.000,.000,.000,.000,.000,.000,.000,
	.278,.278,.355,.556,.556,.889,.667,.191,	// 20
	.333,.333,.389,.584,.278,.333,.278,.278,
	.556,.556,.556,.556,.556,.556,.556,.556,	// 30
	.556,.556,.278,.278,.584,.584,.584,.556,
       1.015,.667,.667,.722,.722,.667,.611,.778,	// 40
	.722,.278,.500,.667,.556,.833,.722,.778,
	.667,.778,.722,.667,.611,.722,.667,.944,	// 50
	.667,.667,.611,.278,.278,.278,.469,.556,
	.333,.556,.556,.500,.556,.556,.278,.556,	// 60
	.556,.222,.222,.500,.222,.833,.556,.556,
	.556,.556,.333,.500,.278,.556,.500,.722,	// 70
	.500,.500,.500,.334,.260,.334,.584,.512]),
// monospace
    mw_tb = new Float32Array([
	.0,.0,.0,.0,.0,.0,.0,.0,		// 00
	.0,.0,.0,.0,.0,.0,.0,.0,
	.0,.0,.0,.0,.0,.0,.0,.0,		// 10
	.0,.0,.0,.0,.0,.0,.0,.0,
	.52,.52,.52,.52,.52,.52,.52,.52,	// 20
	.52,.52,.52,.52,.52,.52,.52,.52,
	.52,.52,.52,.52,.52,.52,.52,.52,	// 30
	.52,.52,.52,.52,.52,.52,.52,.52,
	.52,.52,.52,.52,.52,.52,.52,.52,	// 40
	.52,.52,.52,.52,.52,.52,.52,.52,
	.52,.52,.52,.52,.52,.52,.52,.52,	// 50
	.52,.52,.52,.52,.52,.52,.52,.52,
	.52,.52,.52,.52,.52,.52,.52,.52,	// 60
	.52,.52,.52,.52,.52,.52,.52,.52,
	.52,.52,.52,.52,.52,.52,.52,.52,	// 70
	.52,.52,.52,.52,.52,.52,.52,.52])

/* -- return the character width -- */
function cwid(c, font) {
	var i = c.charCodeAt(0)		// utf-16

	if (i >= 0x80) {		// if not ASCII
		if (i >= 0x300 && i < 0x370)
			return 0;	// combining diacritical mark
		i = 0x61		// 'a'
	}
	return (font || gene.curfont).cw_tb[i]
}
// return the character width with the current font
function cwidf(c) {
	return cwid(c) * gene.curfont.swfac
}

// make XML clean
function clean_txt(p) {
	return p.replace(/<|>|&[^&\s]*?;|&/g, function(c) {
		switch (c) {
		case '<': return "&lt;"
		case '>': return "&gt;"
		case '&': return "&amp;"
		}
		return c		// &xxx;
	})
} // clean_txt()

// estimate the width and height of a string ..
var strwh

(function() {
    if (typeof document != "undefined"
     && abc2svg.el) {

    // .. by the browser

	// change the function
	strwh = function(str) {
		if (str.wh)
			return str.wh

	    var	c,
		el = abc2svg.el,	// hidden <span> created by edit/abcweb/...
		font = gene.curfont,
		h = font.size,
		w = 0,
		n = str.length,
		i0 = 0,
		i = 0

		if (!el.parentElement)		// insert back the <span> in the document
			document.body.appendChild(el)
		el.className = font_class(font)

		if (typeof str == "object") {	// if string already converted
			el.innerHTML = str
			str.wh = [ el.clientWidth, el.clientHeight ]
			return str.wh
		}
		str = clean_txt(str)

		while (1) {
			i = str.indexOf('$', i)
			if (i >= 0) {
				c = str[i + 1]
				if (c == '0') {
					font = gene.deffont
				} else if (c >= '1' && c <= '9') {
					font = get_font("u" + c)
				} else {
					i++
					continue
				}
				el.className = font_class(font)
			}

			el.innerHTML = str.slice(i0, i >= 0 ? i : undefined)
			w += el.clientWidth
//fixme: bad width if space(s) at end of string
			if (el.clientHeight > h)
				h = el.clientHeight

			if (i < 0)
				break
			i += 2;
			i0 = i
		}
		return [w, h]
	}
    } else {

    // .. by internal tables
    strwh = function(str) {
    var	font = gene.curfont,
	swfac = font.swfac,
	h = font.size,
	w = 0,
	i, j, c,
	n = str.length

	for (i = 0; i < n; i++) {
		c = str[i]
		switch (c) {
		case '$':
			c = str[i + 1]
			if (c == '0') {
				font = gene.deffont
			} else if (c >= '1' && c <= '9') {
				font = get_font("u" + c)
			} else {
				c = '$'
				break
			}
			i++;
			swfac = font.swfac
			if (font.size > h)
				h = font.size
			continue
		case '&':
			if (str[i + 1] == ' ')
				break		// normal '&'
			j = str.indexOf(';', i)
			if (j > 0 && j - i < 10) {
				i = j;
				c = 'a'		// XML character reference
			}
			break
		}
		w += cwid(c, font) * swfac
	}
	return [w, h]
    }
  }
})()

// 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	n_font, wh,
	o_font = gene.deffont,
	c_font = gene.curfont,
	o = ""

	// start a '<tspan>' 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\n\tclass="' + cl + '">'
	} // tspan()

	if (c_font != o_font)
		o = tspan(c_font, o_font)
	o += str.replace(/<|>|&[^&\s]*?;|&|\$./g, function(c){
			switch (c) {
			case '<': return "&lt;"
			case '>': return "&gt;"
			case '&': return "&amp;"
			default:
				if (c[0] != '$')
					break
				if (c[1] == '0')
					n_font = gene.deffont
				else if (c[1] >= '1' && c[1] <= '9')
					n_font = get_font("u" + c[1])
				else
					break
				c = ''
				if (n_font == c_font)
					return c
				if (c_font != o_font)
					c = "</tspan>"
				c_font = n_font
				if (c_font == o_font)
					return c
				return c + tspan(c_font, o_font)
			}
			return c		// &xxx;
		})
	if (c_font != o_font)
		o += "</tspan>"

	// convert to String and memorize the string width and height
	o = new String(o)
	if (abc2svg.el)
		strwh(o)		// browser
	else
		o.wh = strwh(str)	// CLI

	gene.curfont = c_font	// keep the current font for the next paragraph

	return o
} // str2svg()

// set the default and current font
function set_font(xxx) {
	if (typeof xxx == "string")
		xxx = get_font(xxx)
	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,		// string or object String with attribute 'wh'
		action,		// default: align left
		w,		// needed for justify
		wh) {		// optional [width, height]
	if (!wh)
		wh = str.wh || strwh(str)
	if (cfmt.singleline || cfmt.trimsvg) {
	    var wx = wh[0]
		switch (action) {
		case 'c':
			wx = wh[0] / 2
			break
		case 'j':
			wx = w
			break
		case 'r':
			wx = 0
			break
		}
		if (img.wx < x + wx)
			img.wx = x + wx
	}

	output += '<text class="' + font_class(gene.deffont)
	if (action != 'j' && str.length > 5
	 && gene.deffont.wadj)
		output += '" lengthAdjust="' + gene.deffont.wadj +
			'" textLength="' + wh[0].toFixed(1);
	output += '" x="';
	out_sxsy(x, '" y="', y)
	switch (action) {
	case 'c':
		output += '" text-anchor="middle">'
		break
	case 'j':
		output += '" textLength="' + w.toFixed(1) + '">'
		break
	case 'r':
		output += '" text-anchor="end">'
		break
	default:
		output += '">'
		break
	}
	out_str(str);
	output += "</text>\n"
}

// move last capitalized word to front when after a comma
function trim_title(title, is_subtitle) {
	var i

	if (cfmt.titletrim) {
		i = title.lastIndexOf(", ")
		if (i < 0 || title[i + 2] < 'A' || title[i + 2] > 'Z') {
			i = 0
		} else if (cfmt.titletrim == 1) {	// (true) compatibility
			if (i < title.length - 7
			 || title.indexOf(' ', i + 3) >= 0)
				i = 0
		} else {
			if (i < title.length - cfmt.titletrim - 2)
				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 (cfmt.titlecaps)
		return title.toUpperCase()
	return title
}

// return the width of the music line
function get_lwidth() {
	if (img.chg)
		set_page()
	return (img.width - img.lm - img.rm
					- 2)	// for bar thickness at eol
			/ cfmt.scale
}

// header generation functions
function write_title(title, is_subtitle) {
    var	h, wh

	if (!title)
		return
	set_page();
	if (is_subtitle) {
		set_font("subtitle");
		h = cfmt.subtitlespace
	} else {
		set_font("title");
		h = cfmt.titlespace
	}
	wh = strwh(title)
	wh[1] += gene.curfont.pad * 2
	vskip(wh[1] + h + gene.curfont.pad)
	h = gene.curfont.pad + wh[1] * .22	// + descent
	if (cfmt.titleleft)
		xy_str(0, h, title, null, null, wh)
	else
		xy_str(get_lwidth() / 2, h, title, "c", null, wh)
}

/* -- output a header format '111 (222)' -- */
function put_inf2r(x, y, str1, str2, action) {
	if (!str1) {
		if (!str2)
			return
		str1 = str2;
		str2 = null
	}
	if (!str2)
		xy_str(x, y, str1, action)
	else
		xy_str(x, y, str1 + ' (' + str2 + ')', action)
}

/* -- write a text block (%%begintext / %%text / %%center) -- */
function write_text(text, action) {
	if (action == 's')
		return				// skip
	set_page();

    var	wh, font, o,
	strlw = get_lwidth(),
		sz = gene.curfont.size,
		lineskip = sz * cfmt.lineskipfac,
		parskip = sz * cfmt.parskipfac,
		i, j, x, words, w, k, ww, str;

	switch (action) {
	default:
//	case 'c':
//	case 'r':
		font = gene.curfont
		switch (action) {
		case 'c': x = strlw / 2; break
		case 'r': x = strlw - font.pad; break
		default: x = font.pad; break
		}
		j = 0
		while (1) {
			i = text.indexOf('\n', j)
			if (i == j) {			// new paragraph
				vskip(parskip);
				blk_flush()
				use_font(gene.curfont)
				while (text[i + 1] == '\n') {
					vskip(lineskip);
					i++
				}
				if (i == text.length)
					break
			} else {
				if (i < 0)
					str = text.slice(j)
				else
					str = text.slice(j, i)
				ww = strwh(str)
				vskip(ww[1] * cfmt.lineskipfac
					+ font.pad * 2)
				xy_str(x, font.pad + ww[1] * .2, str, action)
				if (i < 0)
					break
			}
			j = i + 1
		}
		vskip(parskip);
		blk_flush()
		break
	case 'f':
	case 'j':
		j = 0
		while (1) {
			i = text.indexOf('\n\n', j)
			if (i < 0)
				words = text.slice(j)
			else
				words = text.slice(j, i);
			words = words.split(/\s+/);
			w = k = wh = 0
			for (j = 0; j < words.length; j++) {
				ww = strwh(words[j] + ' ')	// &nbsp;
				w += ww[0]
				if (w >= strlw) {
					vskip(wh * cfmt.lineskipfac)
					xy_str(0, ww[1] * .2,
						words.slice(k, j).join(' '),
						action, strlw,
						[w - ww[0], ww[1]])
					k = j;
					w = ww[0]
					wh = 0
				}
				if (ww[1] > wh)
					wh = ww[1]
			}
			if (w != 0) {			// last line
				vskip(wh * cfmt.lineskipfac)
				xy_str(0, ww[1] * .2, words.slice(k).join(' '))
			}
			vskip(parskip);
			blk_flush()
			if (i < 0)
				break
			while (text[i + 2] == '\n') {
				vskip(lineskip);
				i++
			}
			if (i == text.length)
				break
			use_font(gene.curfont);
			j = i + 2
		}
		break
	}
}

/* -- output the words after tune -- */
function put_words(words) {
    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) {
	    var i = 0,
		k = 0

		if (p[0] == '$'		// if font change
		 && p[1] >= '0' && p[1] <= '9') {
			gene.curfont = p[1] == '0' ? gene.deffont
						: get_font("u" + p[1])
			p = p.slice(2)
		}

		if ((p[i] >= '0' && p[i] <= '9') || p[i + 1] == '.') {
			while (i < p.length) {
				i++
				if (p[i] == ' '
				 || p[i - 1] == ':'
				 || p[i - 1] == '.')
					break
			}
			k = i
			while (p[i] == ' ')
				i++
		}

	    var	y = gene.curfont.size * .22		// descent
		if (k != 0)
			xy_str(x, y, p.slice(0, k), 'r')
		if (i < p.length)
			xy_str(x + 5, y, p.slice(i), 'l')
	} // put_wline()

	// estimate the width of the lines
	words = words.split('\n')
	nw = words.length
	for (i = 0; i < nw; i++) {
		p = words[i]
		if (!p) {
			while (i + 1 < nw && !words[i + 1])
				i++
			n++
		} else if (p.length > maxn) {
			maxn = p.length
			i1 = i		// keep this line
		}
	}
	if (i1 == undefined)
		return			// no text in the W: lines!

	set_font("words")
	vskip(cfmt.wordsspace)
	svg_flush()

	w = get_lwidth() / 2		// half line width
	lw = strwh(words[i1])[0]
	i1 = i2 = 0
	if (lw < w) {			// if 2 columns
		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 -- */
function put_history() {
	var	i, j, c, str, font, h, w, wh, head,
		names = cfmt.infoname.split("\n"),
		n = names.length

	for (i = 0; i < n; i++) {
		c = names[i][0]
		if (cfmt.writefields.indexOf(c) < 0)
			continue
		str = info[c]
		if (!str)
			continue
		if (!font) {
			font = true;
			set_font("history");
			vskip(cfmt.textspace);
			h = gene.curfont.size * cfmt.lineskipfac
		}
		head = names[i].slice(2)
		if (head[0] == '"')
			head = head.slice(1, -1);
		vskip(h);
		wh = strwh(head);
		xy_str(0, wh[1] * .22, head, null, null, wh);
		w = wh[0];
		str = str.split('\n');
		xy_str(w, wh[1] * .22, str[0])
		for (j = 1; j < str.length; j++) {
			if (!str[j]) {			// new paragraph
				vskip(gene.curfont.size * cfmt.parskipfac)
				continue
			}
			vskip(h);
			xy_str(w, wh[1] * .22, str[j])
		}
		vskip(h * cfmt.parskipfac)
		use_font(gene.curfont)
	}
}

// build a new sequence of the parts with clearer names
function part_seq() {
    var	i,
	o = ""

	for (i = 0; i < info.P.length; i++) {
		if (i)
			o += ' '
		o += partname(info.P[i])[1]
	}
	return o
} // part_seq()

// get the meaningful names of a part (P:)
function partname(c) {
    var	i, r, tmp

    if (cfmt.partname) {
	tmp = cfmt.partname.split('\n')

	for (i = 0; i < tmp.length; i++) {
		if (tmp[i][0] == c) {
			r = tmp[i].match(/.\s+(\S+)\s*(.+)?/)
			break
		}
	}
    }
	if (!r)
		return [0, c, c]
	if (!r[2])
		r[2] = r[1]
	if (r[2][0] == '"')
		r[2] = r[2].slice(1, -1)
	return r
} // partname()

/* -- output the tune heading -- */
// (possible hook)
Abc.prototype.tunhd = function() {
    var	i, j, area, composer, origin, rhythm, down1, down2, p,
		lwidth = get_lwidth()

	vskip(cfmt.topspace)

	/* titles */
	if (info.T
	 && cfmt.writefields.indexOf('T') >= 0) {
		i = 0
		while (1) {
			j = info.T.indexOf("\n", i)
			if (j < 0) {
				write_title(info.T.substring(i), i != 0)
				break
			}
			write_title(info.T.slice(i, j), i != 0);
			i = j + 1
		}
	}

	/* rhythm, composer, origin */
	down1 = down2 = 0
	if (parse.ckey.k_bagpipe
	 && !cfmt.infoline
	 && cfmt.writefields.indexOf('R') >= 0)
		rhythm = info.R
	if (rhythm) {
		set_font("composer");
		down1 = cfmt.composerspace + gene.curfont.size + 2
		xy_str(0, -down1 + gene.curfont.size *.22, rhythm)
	}
	area = info.A
	if (cfmt.writefields.indexOf('C') >= 0)
		composer = info.C
	if (cfmt.writefields.indexOf('O') >= 0)
		origin = info.O
	if (composer || origin || cfmt.infoline) {
		var xcomp, align;

		set_font("composer");
		if (cfmt.aligncomposer < 0) {
			xcomp = 0;
			align = ' '
		} else if (cfmt.aligncomposer == 0) {
			xcomp = lwidth * .5;
			align = 'c'
		} else {
			xcomp = lwidth;
			align = 'r'
		}
		if (composer || origin) {
			down2 = cfmt.composerspace + 2
			i = 0
			while (1) {
				down2 += gene.curfont.size
				if (composer)
					j = composer.indexOf("\n", i)
				else
					j = -1
				if (j < 0) {
					put_inf2r(xcomp, -down2 + gene.curfont.size *.22,
						composer ? composer.substring(i) : null,
						origin,
						align)
					break
				}
				xy_str(xcomp, -down2 + gene.curfont.size *.22,
					composer.slice(i, j), align);
				i = j + 1
			}
		}

		rhythm = rhythm ? null : info.R
		if ((rhythm || area) && cfmt.infoline) {

			/* if only one of rhythm or area then do not use ()'s
			 * otherwise output 'rhythm (area)' */
			set_font("info");
			down2 += cfmt.infospace + gene.curfont.size
			put_inf2r(lwidth, -down2 + gene.curfont.size *.22,
				rhythm, area, 'r')
		}
	}

	/* parts */
	if (info.P
	 && cfmt.writefields.indexOf('P') >= 0) {
		set_font("parts");
		i = cfmt.partsspace + gene.curfont.size + gene.curfont.pad
		if (down1 + i > down2)
			down2 = down1 + i
		else
			down2 += i
		p = info.P
		if (cfmt.partname)
			p = part_seq()
		xy_str(0, -down2 + gene.curfont.size *.22, p)
		down2 += gene.curfont.pad
	} else if (down1 > down2) {
		down2 = down1
	}
	vskip(down2 + cfmt.musicspace)
} // tunhd()

// output the tune header
function write_heading() {
	vskip(cfmt.topspace)
	self.tunhd()
} // write_heading()
// abc2svg - svg.js - svg functions
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

var	output = "",		// output buffer
	style = '\
\n.stroke{stroke:currentColor;fill:none}\
\n.bW{stroke:currentColor;fill:none;stroke-width:1}\
\n.bthW{stroke:currentColor;fill:none;stroke-width:3}\
\n.slW{stroke:currentColor;fill:none;stroke-width:.7}\
\n.slthW{stroke:currentColor;fill:none;stroke-width:1.5}\
\n.sltnW{stroke:currentColor;fill:none;stroke-width:.25}\
\n.sldW{stroke:currentColor;fill:none;stroke-width:.7;stroke-dasharray:5,10}\
\n.sW{stroke:currentColor;fill:none;stroke-width:.7}\
\n.box{outline:1px solid black;outline-offset:1px}',
	font_style = '',
	posx = cfmt.leftmargin / cfmt.scale,	// default x offset of the images
	posy = 0,		// y offset in the block
	img = {			// image
		width: cfmt.pagewidth,	// width
		lm: cfmt.leftmargin,	// left and right margins
		rm: cfmt.rightmargin,
		wx: 0,			// used width between the left and right margins
		chg: 1 //true
	},
	defined_glyph = {},
	defs = '',
	fulldefs = '',		// unreferenced defs as <filter>
	stv_g = {		/* staff/voice graphic parameters */
		scale: 1,
		stsc: 1,	// staff scale
		vsc: 1,		// voice scale
		dy: 0,
		st: -1,
		v: -1,
		g: 0
//		color: undefined
	},
	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"},
  lphr: {x:0, y:23, c:"\ue030"},
  mphr: {x:0, y:23, c:"\ue038"},
  sphr: {x:0, y:25, c:"\ue039"},
  short: {x:0, y:32, c:"\ue038"},
  tick: {x:0, y:25, c:"\ue039"},
  rdots: {x:0, y:0, c:"\ue043"},	// repeat dots
  rdot: {x:0, y:0, c:"\ue044"},		// single repeat dot
  dsgn: {x:-12, y:0, c:"\ue045"},	// D.S.
  dcap: {x:-12, y:0, c:"\ue046"},	// D.C.
  sgno: {x:-5, y:0, c:"\ue047"},	// segno
  coda: {x:-10, y:0, c:"\ue048"},
  tclef: {x:-8, y:0, c:"\ue050"},
  cclef: {x:-8, y:0, c:"\ue05c"},
  bclef: {x:-8, y:0, c:"\ue062"},
  pclef: {x:-6, y:0, c:"\ue069"},
  spclef: {x:-6, y:0, c:"\ue069"},
  stclef: {x:-8, y:0, c:"\ue07a"},
  scclef: {x:-8, y:0, c:"\ue07b"},
  sbclef: {x:-7, y:0, c:"\ue07c"},
  oct: {x:0, y:2, c:"\ue07d"},		// 8 for clefs
  oct2: {x:0, y:2, c:"\ue07e"},		// 15 for clefs
  mtr0: {x:0, y:0, c:"\ue080"},		// meters
  mtr1: {x:0, y:0, c:"\ue081"},
  mtr2: {x:0, y:0, c:"\ue082"},
  mtr3: {x:0, y:0, c:"\ue083"},
  mtr4: {x:0, y:0, c:"\ue084"},
  mtr5: {x:0, y:0, c:"\ue085"},
  mtr6: {x:0, y:0, c:"\ue086"},
  mtr7: {x:0, y:0, c:"\ue087"},
  mtr8: {x:0, y:0, c:"\ue088"},
  mtr9: {x:0, y:0, c:"\ue089"},
  mtrC: {x:0, y:0, c:"\ue08a"},		// common time (4/4)
  "mtrC|": {x:0, y:0, c:"\ue08b"},	// cut time (2/2)
  "mtr+":  {x:0, y:0, c:"\ue08c"},
  "mtr(":  {x:0, y:0, c:"\ue094"},
  "mtr)":  {x:0, y:0, c:"\ue095"},
  HDD: {x:-7, y:0, c:"\ue0a0"},
  breve: {x:-7, y:0, c:"\ue0a1"},
  HD: {x:-5.2, y:0, c:"\ue0a2"},
  Hd: {x:-3.8, y:0, c:"\ue0a3"},
  hd: {x:-3.7, y:0, c:"\ue0a4"},
  ghd: {x:2, y:0, c:"\ue0a4", sc:.66},	// grace note head
  pshhd: {x:-3.7, y:0, c:"\ue0a9"},
  pfthd: {x:-3.7, y:0, c:"\ue0b3"},
  x: {x:-3.7, y:0, c:"\ue0a9"},		// 'x' note head
  "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:-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"},
  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
 "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_2": {x:-2, y:0, c:"\ue280"},	// quarter-tone flat
 "acc-3_2": {x:-3, y:0, c:"\ue281"},	// three-quarter-tones flat
  acc1_2: {x:-1, y:0, c:"\ue282"},	// quarter-tone sharp
  acc3_2: {x:-3, y:0, c:"\ue283"},	// three-quarter-tones sharp
  accent: {x:-3, y:2, c:"\ue4a0"},
  stc: {x:0, y:-2, c:"\ue4a2"},		// staccato
  emb: {x:0, y:-2, c:"\ue4a4"},
  wedge: {x:0, y:0, c:"\ue4a8"},
  marcato: {x:-3, y:-2, c:"\ue4ac"},
  hld: {x:-7, y:-2, c:"\ue4c0"},		// fermata
  brth: {x:0, y:0, c:"\ue4ce"},
  caes: {x:0, y:8, c:"\ue4d1"},
  r00: {x:-1.5, y:0, c:"\ue4e1"},
  r0: {x:-1.5, y:0, c:"\ue4e2"},
  r1: {x:-3.5, y:-6, c:"\ue4e3"},
  r2: {x:-3.2, y:0, c:"\ue4e4"},
  r4: {x:-3, y:0, c:"\ue4e5"},
  r8: {x:-3, y:0, c:"\ue4e6"},
  r16: {x:-4, y:0, c:"\ue4e7"},
  r32: {x:-4, y:0, c:"\ue4e8"},
  r64: {x:-4, y:0, c:"\ue4e9"},
  r128: {x:-4, y:0, c:"\ue4ea"},
//  mrest: {x:-10, y:0, c:"\ue4ee"},
  mrep: {x:-6, y:0, c:"\ue500"},
  mrep2: {x:-9, y:0, c:"\ue501"},
  p: {x:-3, y:0, c:"\ue520"},
  f: {x:-3, y:0, c:"\ue522"},
  pppp: {x:-15, y:0, c:"\ue529"},
  ppp: {x:-14, y:0, c:"\ue52a"},
  pp: {x:-8, y:0, c:"\ue52b"},
  mp: {x:-8, y:0, c:"\ue52c"},
  mf: {x:-8, y:0, c:"\ue52d"},
  ff: {x:-7, y:0, c:"\ue52f"},
  fff: {x:-10, y:0, c:"\ue530"},
  ffff: {x:-14, y:0, c:"\ue531"},
  sfz: {x:-10, y:0, c:"\ue539"},
  trl: {x:-3, y:-3, c:"\ue566"},	// trill
  turn: {x:-5, y:0, c:"\ue567"},
  turnx: {x:-5, y:0, c:"\ue569"},
  umrd: {x:-6, y:2, c:"\ue56c"},
  lmrd: {x:-6, y:2, c:"\ue56d"},
  dplus: {x:-3, y:0, c:"\ue582"},	// plus
  sld: {x:-3, y:2, c:"\ue5d0"},		// slide
  grm: {x:-3, y:-2, c:"\ue5e2"},	// grace mark
  dnb: {x:-3, y:0, c:"\ue610"},		// down bow
  upb: {x:-2, y:0, c:"\ue612"},		// up bow
  opend: {x:-2, y:-2, c:"\ue614"},	// harmonic
  roll: {x:0, y:0, c:"\ue618"},
  thumb: {x:-2, y:-2, c:"\ue624"},
  snap: {x:-2, y:-2, c:"\ue630"},
  ped: {x:-10, y:0, c:"\ue650"},
  pedoff: {x:-5, y:0, c:"\ue655"},
 "mtro.": {x:0, y:0, c:"\ue910"},	// tempus perfectum prolatione perfecta
  mtro:   {x:0, y:0, c:"\ue911"},		// tempus perfectum
 "mtro|": {x:0, y:0, c:"\ue912"},	// tempus perfectum (twice as fast)
 "mtrc.": {x:0, y:0, c:"\ue914"},	// tempus imperfectum prolatione perfecta
  mtrc:   {x:0, y:0, c:"\ue915"},	// tempus imperfectum
 "mtrc|": {x:0, y:0, c:"\ue918"},	// tempus imperfectum (twice as fast)
  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 <defs>
var glyphs = {
}

// convert a meter string to a SmuFL encoded string
function m_gl(s) {
	return s.replace(/./g,
		function(e) {
		    var	m = tgls["mtr" + e]
//fixme: !! no m.x nor m.y yet !!
//			if (!m.x && !m.y)
				return m ? m.c : 0
//			return '<tspan dx="'+ m.x.toFixed(1) +
//				'" dy="' + m.y.toFixed(1) +
//				'">' +
//				m.c + '</tspan>'
		})
}

// mark a glyph as used and add it in <defs>
function def_use(gl) {
	var	i, j, g

	if (defined_glyph[gl])
		return
	defined_glyph[gl] = true;
	g = glyphs[gl]
	if (!g) {
//throw new Error("unknown glyph: " + gl)
		error(1, null, "Unknown glyph: '$1'", gl)
		return	// fixme: the xlink is set
	}
	j = 0
	while (1) {
		i = g.indexOf('xlink:href="#', j)
		if (i < 0)
			break
		i += 13;
		j = g.indexOf('"', i);
		def_use(g.slice(i, j))
	}
	defs += '\n' + g
}

// add user defs from %%beginsvg
function defs_add(text) {
	var	i, j, gl, tag, is,
		ie = 0

	// remove XML comments
	text = text.replace(/<!--.*?-->/g, '')

	while (1) {
		is = text.indexOf('<', ie);
		if (is < 0)
			break
		i = text.indexOf('id="', is)
		if (i < 0)
			break
		i += 4;
		j = text.indexOf('"', i);
		if (j < 0)
			break
		gl = text.slice(i, j);
		ie = text.indexOf('>', j);
		if (ie < 0)
			break
		if (text[ie - 1] == '/') {
			ie++
		} else {
			i = text.indexOf(' ', is);
			if (i < 0)
				break
			tag = text.slice(is + 1, i);
			ie = text.indexOf('</' + tag + '>', ie)
			if (ie < 0)
				break
			ie += 3 + tag.length
		}
		if (text.substr(is, 7) == '<filter')
			fulldefs += text.slice(is, ie) + '\n'
		else
			glyphs[gl] = text.slice(is, ie)
	}
}

// output the stop/start of a graphic sequence
function set_g() {

	// close the previous sequence
	if (stv_g.started) {
		stv_g.started = false;
		glout()
		output += "</g>\n"
	}

	// check if new sequence needed
	if (stv_g.scale == 1 && !stv_g.color)
		return

	// open the new sequence
	glout()
	output += '<g '
	if (stv_g.scale != 1) {
		if (stv_g.st < 0)
			output += voice_tb[stv_g.v].scale_str
		else if (stv_g.v < 0)
			output += staff_tb[stv_g.st].scale_str
		else
			output += 'transform="translate(0,' +
					(posy - stv_g.dy).toFixed(1) +
				') scale(' + stv_g.scale + ')"'
	}
	if (stv_g.color) {
		if (stv_g.scale != 1)
			output += ' ';
		output += 'color="' + stv_g.color + '"'
	}
	output += ">\n";
	stv_g.started = true
}

/* set the color */
function set_color(color) {
	if (color == stv_g.color)
		return undefined	// same color
	var	old_color = stv_g.color;
	stv_g.color = color;
	set_g()
	return old_color
}

/* -- set the staff scale (only) -- */
function set_sscale(st) {
	var	new_scale, dy

	if (st != stv_g.st && stv_g.scale != 1)
		stv_g.scale = 1
	new_scale = st >= 0 ? staff_tb[st].staffscale : 1
	if (st >= 0 && new_scale != 1)
		dy = staff_tb[st].y
	else
		dy = posy
	if (new_scale == stv_g.scale && dy == stv_g.dy
	 && stv_g.st == st && stv_g.vsc == 1)
		return
	stv_g.stsc =
		stv_g.scale = new_scale
	stv_g.vsc = 1
	stv_g.dy = dy;
	stv_g.st = st;
	stv_g.v = -1;
	set_g()
}

/* -- set the voice or staff scale -- */
function set_scale(s) {
    var	new_dy = posy,
	st = staff_tb[s.st].staffscale == 1 ? -1 : s.st,
	new_scale = s.p_v.scale

	if (st >= 0) {
		new_scale *= staff_tb[st].staffscale
		new_dy = staff_tb[st].y
	}
	if (new_scale == stv_g.scale && stv_g.dy == new_dy)
		return
	stv_g.scale = new_scale;
	stv_g.vsc = s.p_v.scale
	stv_g.dy = new_dy;
	stv_g.st = st
	stv_g.v = s.v;
	set_g()
}

// -- set the staff output buffer and scale when delayed output
function set_dscale(st, no_scale) {
	if (output) {
		if (stv_g.started) {	// close the previous sequence
			stv_g.started = false
			glout()
			output += "</g>\n"
		}
		if (stv_g.st < 0) {
			staff_tb[0].output += output
		} else if (stv_g.scale == 1) {
			staff_tb[stv_g.st].output += output
		} else {
			staff_tb[stv_g.st].sc_out += output
		}
		output = ""
	}
	if (st < 0)
		stv_g.scale = 1
	else
		stv_g.scale = no_scale ? 1 : staff_tb[st].staffscale;
	stv_g.st = st;
	stv_g.dy = 0
}

// update the y offsets of delayed output
function delayed_update() {
	var st, new_out, text

	for (st = 0; st <= nstaff; st++) {
		if (staff_tb[st].sc_out) {
			output += '<g ' + staff_tb[st].scale_str + '>\n' +
				staff_tb[st].sc_out + '</g>\n';
			staff_tb[st].sc_out = ""
		}
		if (!staff_tb[st].output)
			continue
		output += '<g transform="translate(0,' +
				(-staff_tb[st].y).toFixed(1) +
				')">\n' +
			staff_tb[st].output +
			'</g>\n';
		staff_tb[st].output = ""
	}
}

// output the annotations
function anno_out(s, t, f) {
	if (s.istart == undefined)
		return
	var	type = s.type,
		h = s.ymx - s.ymn + 4,
		wl = s.wl || 2,
		wr = s.wr || 2

	if (s.grace)
		type = C.GRACE

	f(t || abc2svg.sym_name[type], s.istart, s.iend,
		s.x - wl - 2, staff_tb[s.st].y + s.ymn + h - 2,
		wl + wr + 4, h, s);
}

function a_start(s, t) {
	anno_out(s, t, user.anno_start)
}
function a_stop(s, t) {
	anno_out(s, t, user.anno_stop)
}
function empty_function() {
}
	// the values are updated on generate()
    var	anno_start = empty_function,
	anno_stop = empty_function

// output the stop user annotations
function anno_put() {
    var	s
	while (1) {
		s = anno_a.shift()
		if (!s)
			break
		switch (s.type) {
		case C.CLEF:
		case C.METER:
		case C.KEY:
		case C.REST:
			if (s.type != C.REST || s.rep_nb) {
				set_sscale(s.st)
				break
			}
			// fall thru
		case C.GRACE:
		case C.NOTE:
		case C.MREST:
			set_scale(s)
			break
//		default:
//			continue
		}
		anno_stop(s)
	}
} // anno_put()

// output a string with x, y, a and b
// In the string,
//	X and Y are replaced by scaled x and y
//	A and B are replaced by a and b as string
//	F and G are replaced by a and b as float
function out_XYAB(str, x, y, a, b) {
	x = sx(x);
	y = sy(y);
	output += str.replace(/X|Y|A|B|F|G/g, function(c) {
		switch (c) {
		case 'X': return x.toFixed(1)
		case 'Y': return y.toFixed(1)
		case 'A': return a
		case 'B': return b
		case 'F': return a.toFixed(1)
//		case 'G':
		default: return b.toFixed(1)
		}
		})
}

// open / close containers
function g_open(x, y, rot, sx, sy) {
	glout()
	out_XYAB('<g transform="translate(X,Y', x, y);
	if (rot)
		output += ') rotate(' + rot.toFixed(2)
	if (sx) {
		output += ') scale(' + sx
		if (sy)
			output += ', ' + sy
	}
	output += ')">\n';
	stv_g.g++
}
function g_close() {
	glout()
	stv_g.g--;
	output += '</g>\n'
}

// external SVG string
Abc.prototype.out_svg = function(str) { output += str }

// exported functions for the annotation
function sx(x) {
	if (stv_g.g)
		return x
	return (x + posx) / stv_g.scale
}
Abc.prototype.sx = sx
function sy(y) {
	if (stv_g.g)
		return -y
	if (stv_g.scale == 1)
		return posy - y
	if (stv_g.v >= 0)
		return (stv_g.dy - y) / stv_g.vsc
	return stv_g.dy - y	// staff scale only
}
Abc.prototype.sy = sy;
Abc.prototype.sh = function(h) {
	if (stv_g.st < 0)
		return h / stv_g.scale
	return h
}
// for absolute X,Y coordinates
Abc.prototype.ax = function(x) { return x + posx }
Abc.prototype.ay = function(y) {
	if (stv_g.st < 0)
		return posy - y
	return posy + (stv_g.dy - y) * stv_g.scale - stv_g.dy
}
Abc.prototype.ah = function(h) {
	if (stv_g.st < 0)
		return h
	return h * stv_g.scale
}
// output scaled (x + <sep> + y)
function out_sxsy(x, sep, y) {
	x = sx(x);
	y = sy(y);
	output += x.toFixed(1) + sep + y.toFixed(1)
}
Abc.prototype.out_sxsy = out_sxsy

// define the start of a path
function xypath(x, y, fill) {
	if (fill)
		out_XYAB('<path d="mX Y', x, y)
	else
		out_XYAB('<path class="stroke" d="mX Y', x, y)
}
Abc.prototype.xypath = xypath

// 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 +=
				    '<path class="stroke" stroke-width="1" d="M' +
					xp.toFixed(1) + ' ' +
					sy(p_st.y + d * i).toFixed(1)
				dx2 = 0
				while (1) {
					hl = hll.shift()
					if (!hl)
						break
					x2 = sx(hl[0])
					output += 'm' +
						(x2 - xp + hl[1] - dx2).toFixed(2) +
						' 0h' + (-hl[1] + hl[2]).toFixed(2)
					xp = x2
					dx2 = hl[2]
				}
				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()

// output the list of glyphs and the stems
// [0] = x glyph
// [1] = y glyph
// [2] = glyph code
// [3] = x, y, h of stem (3 values per stem)
var gla = [[], [], "", [], [], []]
function glout() {
    var	e,
	v = []

	// glyphs (notes, accidentals...)
    if (gla[0].length) {
	while (1) {
		e = gla[0].shift()
		if (e == undefined)
			break
		v.push(e.toFixed(1))
	}
	output += '<text x="' + v.join(',')

	v = []
	while (1) {
		e = gla[1].shift()
		if (e == undefined)
			break
		v.push(e.toFixed(1))
	}
	output += '"\ny="' + v.join(',')

	output += '"\n>' + gla[2] + '</text>\n'
	gla[2] = ""
    }

	// stems
	if (!gla[3].length)
		return
	output += '<path class="sW" d="'
	while (1) {
		e = gla[3].shift()
		if (e == undefined)
			break
		output += 'M' + e.toFixed(1) +
			' ' + gla[3].shift().toFixed(1) +
			'v' + gla[3].shift().toFixed(1)
	}
	output += '"/>\n'
} // glout()

// output a glyph
function xygl(x, y, gl) {
// (avoid ps<->js loop)
//	if (psxygl(x, y, gl))
//		return
	if (glyphs[gl]) {
		def_use(gl)
		out_XYAB('<use x="X" y="Y" xlink:href="#A"/>\n', x, y, gl)
	} else {
	    var	tgl = tgls[gl]
		if (tgl) {
			x += tgl.x * stv_g.scale;
			y -= tgl.y
			if (tgl.sc) {
				out_XYAB('<text transform="translate(X,Y) scale(A)">B</text>\n',
					x, y, tgl.sc, tgl.c);
			} else {
//				out_XYAB('<text x="X" y="Y">A</text>\n', x, y, tgl.c)
				gla[0].push(sx(x))
				gla[1].push(sy(y))
				gla[2] += tgl.c
			}
		} else if (gl != 'nil') {
			error(1, null, 'no definition of $1', gl)
		}
	}
}
// - specific functions -
// gua gda (acciaccatura)
function out_acciac(x, y, dx, dy, up) {
	if (up) {
		x -= 1;
		y += 4
	} else {
		x -= 5;
		y -= 4
	}
	out_XYAB('<path class="stroke" d="mX YlF G"/>\n',
		x, y, dx, -dy)
}
// staff system brace
function out_brace(x, y, h) {
//fixme: '-6' depends on the scale
	x += posx - 6;
	y = posy - y;
	h /= 24;
	output += '<text transform="translate(' +
				x.toFixed(1) + ',' + y.toFixed(1) +
			') scale(2.5,' + h.toFixed(2) +
			')">' + tgls.brace.c + '</text>\n'
}

// staff system bracket
function out_bracket(x, y, h) {
	x += posx - 5;
	y = posy - y - 3;
	h += 2;
	output += '<path d="m' + x.toFixed(1) + ' ' + y.toFixed(1) + '\n\
	c10.5 1 12 -4.5 12 -3.5c0 1 -3.5 5.5 -8.5 5.5\n\
	v' + h.toFixed(1) + '\n\
	c5 0 8.5 4.5 8.5 5.5c0 1 -1.5 -4.5 -12 -3.5"/>\n'
}
// hyphen
function out_hyph(x, y, w) {
	var	n, a_y,
		d = 25 + ((w / 20) | 0) * 3

	if (w > 15.)
		n = ((w - 15) / d) | 0
	else
		n = 0;
	x += (w - d * n - 5) / 2;
	out_XYAB('<path class="stroke" stroke-width="1.2"\n\
	stroke-dasharray="5,A"\n\
	d="mX YhB"/>\n',
		x, y + 4,		// set the line a bit upper
		Math.round((d - 5) / stv_g.scale), d * n + 5)
}
// stem [and flags]
function out_stem(x, y, h, grace,
		  nflags, straight) {	// optional
//fixme: dx KO with half note or longa
	var	dx = grace ? GSTEM_XOFF : 3.5,
		slen = -h

	if (h < 0)
		dx = -dx;		// down
	x += dx * stv_g.scale
	if (stv_g.v >= 0)
		slen /= voice_tb[stv_g.v].scale;
	gla[3].push(sx(x))
	gla[3].push(sy(y))
	gla[3].push(slen)
	if (!nflags)
		return

	y += h
	if (h > 0) {				// up
		if (!straight) {
			if (!grace) {
				xygl(x, y, "flu" + nflags)
				return
			} else {		// grace
				output += '<path d="'
				if (nflags == 1) {
					out_XYAB('MX Yc0.6 3.4 5.6 3.8 3 10\n\
	1.2 -4.4 -1.4 -7 -3 -7\n', x, y)
				} else {
					while (--nflags >= 0) {
						out_XYAB('MX Yc1 3.2 5.6 2.8 3.2 8\n\
	1.4 -4.8 -2.4 -5.4 -3.2 -5.2\n', x, y);
						y -= 3.5
					}
				}
			}
		} else {			// straight
			output += '<path d="'
			if (!grace) {
				while (--nflags >= 0) {
					out_XYAB('MX Yl7 3.2 0 3.2 -7 -3.2z\n',
						x, y);
					y -= 5.4
				}
			} else {		// grace
				while (--nflags >= 0) {
					out_XYAB('MX Yl3 1.5 0 2 -3 -1.5z\n',
						x, y);
					y -= 3
				}
			}
		}
	} else {				// down
		if (!straight) {
			if (!grace) {
				xygl(x, y, "fld" + nflags)
				return
			} else {		// grace
				output += '<path d="'
				if (nflags == 1) {
					out_XYAB('MX Yc0.6 -3.4 5.6 -3.8 3 -10\n\
	1.2 4.4 -1.4 7 -3 7\n', x, y)
				} else {
					while (--nflags >= 0) {
						out_XYAB('MX Yc1 -3.2 5.6 -2.8 3.2 -8\n\
	1.4 4.8 -2.4 5.4 -3.2 5.2\n', x, y);
						y += 3.5
					}
				}
			}
		} else {			// straight
			output += '<path d="'
			if (!grace) {
				while (--nflags >= 0) {
					out_XYAB('MX Yl7 -3.2 0 -3.2 -7 3.2z\n',
						x, y);
					y += 5.4
				}
//			} else {		// grace
//--fixme: error?
			}
		}
	}
	output += '"/>\n'
}
// tremolo
function out_trem(x, y, ntrem) {
	out_XYAB('<path d="mX Y\n\t', x - 4.5, y)
	while (1) {
		output += 'l9 -3v3l-9 3z'
		if (--ntrem <= 0)
			break
		output += 'm0 5.4'
	}
	output += '"/>\n'
}
// tuplet bracket - the staves are not defined
function out_tubr(x, y, dx, dy, up) {
	var	h = up ? -3 : 3;

	y += h;
	dx /= stv_g.scale;
	output += '<path class="stroke" d="m';
	out_sxsy(x, ' ', y);
	output += 'v' + h.toFixed(1) +
		'l' + dx.toFixed(1) + ' ' + (-dy).toFixed(1) +
		'v' + (-h).toFixed(1) + '"/>\n'
}
// tuplet bracket with number - the staves are not defined
function out_tubrn(x, y, dx, dy, up, str) {
    var	dxx,
	sw = str.length * 10,
	h = up ? -3 : 3;

	set_font("tuplet")
	xy_str(x + dx / 2, y + dy / 2 - gene.curfont.size * .1,
		str, 'c')
		dx /= stv_g.scale
	if (!up)
		y += 6;
	output += '<path class="stroke" d="m';
	out_sxsy(x, ' ', y);
	dxx = dx - sw + 1
	if (dy > 0)
		sw += dy / 8
	else
		sw -= dy / 8
	output += 'v' + h.toFixed(1) +
		'm' + dx.toFixed(1) + ' ' + (-dy).toFixed(1) +
		'v' + (-h).toFixed(1) + '"/>\n' +
		'<path class="stroke" stroke-dasharray="' +
		(dxx / 2).toFixed(1) + ' ' + sw.toFixed(1) +
		'" d="m';
	out_sxsy(x, ' ', y - h);
	output += 'l' + dx.toFixed(1) + ' ' + (-dy).toFixed(1) + '"/>\n'

}
// underscore line
function out_wln(x, y, w) {
	out_XYAB('<path class="stroke" stroke-width="0.8" d="mX YhF"/>\n',
		x, y + 1, w)
}

// decorations with string
var deco_str_style = {
crdc:	{				// cresc., decresc., dim., ...
		dx: 0,
		dy: 5,
		style: 'font:italic 14px text,serif',
		anchor: ' text-anchor="middle"'
	},
dacs:	{				// long repeats (da capo, fine...)
		dx: 0,
		dy: 3,
		style: 'font:bold 15px text,serif',
		anchor: ' text-anchor="middle"'
	},
pf:	{
		dx: 0,
		dy: 5,
		style: 'font:italic bold 16px text,serif',
		anchor: ' text-anchor="middle"'
	}
}
deco_str_style.at = deco_str_style.crdc

function out_deco_str(x, y, de) {
    var	name = de.dd.glyph			// class

	if (name == 'fng') {
		out_XYAB('\
<text x="X" y="Y" style="font-size:14px">A</text>\n',
			x - 2, y + 1, m_gl(de.dd.str))
		return
	}

	if (name == '@') {			// compatibility
		name = 'at'
	} else if (!/^[A-Za-z][A-Za-z\-_]*$/.test(name)) {
		error(1, de.s, "No function for decoration '$1'", de.dd.name)
		return
	}

    var	f,
		a_deco = deco_str_style[name]

	if (!a_deco)
		a_deco = deco_str_style.crdc	// default style
	else if (a_deco.style)
		style += "\n." + name + "{" + a_deco.style + "}",
		delete a_deco.style

	x += a_deco.dx;
	y += a_deco.dy;
	out_XYAB('<text x="X" y="Y" class="A"B>', x, y,
		name, a_deco.anchor || "");
	set_font("annotation");
	out_str(de.dd.str)
	output += '</text>\n'
}

function out_arp(x, y, val) {
	g_open(x, y, 270);
	x = 0;
	val = Math.ceil(val / 6)
	while (--val >= 0) {
		xygl(x, 6, "ltr");
		x += 6
	}
	g_close()
}
function out_cresc(x, y, val, defl) {
	x += val * stv_g.scale
	val = -val;
	out_XYAB('<path class="stroke"\n\
	d="mX YlF ', x, y, val)
	if (defl.nost)
		output += '-2.2m0 -3.6l' + (-val).toFixed(1) + ' -2.2"/>\n'
	else
		output += '-4l' + (-val).toFixed(1) + ' -4"/>\n'

}
function out_dim(x, y, val, defl) {
	out_XYAB('<path class="stroke"\n\
	d="mX YlF ', x, y, val)
	if (defl.noen)
		output += '-2.2m0 -3.6l' + (-val).toFixed(1) + ' -2.2"/>\n'
	else
		output += '-4l' + (-val).toFixed(1) + ' -4"/>\n'
}
function out_ltr(x, y, val) {
	y += 4;
	val = Math.ceil(val / 6)
	while (--val >= 0) {
		xygl(x, y, "ltr");
		x += 6
	}
}
Abc.prototype.out_lped = function(x, y, val, defl) {
	if (!defl.nost)
		xygl(x, y, "ped");
	if (!defl.noen)
		xygl(x + val + 6, y, "pedoff")
}
function out_8va(x, y, val, defl) {
	if (val < 18) {
		val = 18
		x -= 4
	}
	if (!defl.nost) {
		out_XYAB('<text x="X" y="Y" \
style="font:italic bold 12px text,serif">8\
<tspan dy="-4" style="font-size:10px">va</tspan></text>\n',
			x - 8, y);
		x += 12;
		val -= 12
	}
	y += 6;
	out_XYAB('<path class="stroke" stroke-dasharray="6,6" d="mX YhF"/>\n',
		x, y, val)
	if (!defl.noen)
		out_XYAB('<path class="stroke" d="mX Yv6"/>\n', x + val, y)
}
function out_8vb(x, y, val, defl) {
	if (val < 18) {
		val = 18
		x -= 4
	}
	if (!defl.nost) {
		out_XYAB('<text x="X" y="Y" \
style="font:italic bold 12px text,serif">8\
<tspan dy=".5" style="font-size:10px">vb</tspan></text>\n',
			x - 8, y);
		x += 10
		val -= 10
	}
//	y -= 2;
	out_XYAB('<path class="stroke" stroke-dasharray="6,6" d="mX YhF"/>\n',
		x, y, val)
	if (!defl.noen)
		out_XYAB('<path class="stroke" d="mX Yv-6"/>\n', x + val, y)
}
function out_15ma(x, y, val, defl) {
	if (val < 25) {
		val = 25
		x -= 6
	}
	if (!defl.nost) {
		out_XYAB('<text x="X" y="Y" \
style="font:italic bold 12px text,serif">15\
<tspan dy="-4" style="font-size:10px">ma</tspan></text>\n',
			x - 10, y);
		x += 20;
		val -= 20
	}
	y += 6;
	out_XYAB('<path class="stroke" stroke-dasharray="6,6" d="mX YhF"/>\n',
		x, y, val)
	if (!defl.noen)
		out_XYAB('<path class="stroke" d="mX Yv6"/>\n', x + val, y)
}
function out_15mb(x, y, val, defl) {
	if (val < 24) {
		val = 24
		x -= 5
	}
	if (!defl.nost) {
		out_XYAB('<text x="X" y="Y" \
style="font:italic bold 12px text,serif">15\
<tspan dy=".5" style="font-size:10px">mb</tspan></text>\n',
			x - 10, y);
		x += 18
		val -= 18
	}
//	y -= 2;
	out_XYAB('<path class="stroke" stroke-dasharray="6,6" d="mX YhF"/>\n',
		x, y, val)
	if (!defl.noen)
		out_XYAB('<path class="stroke" d="mX Yv-6"/>\n', x + val, y)
}
var deco_val_tb = {
	arp:	out_arp,
	cresc:	out_cresc,
	dim:	out_dim,
	ltr:	out_ltr,
	lped:	function(x, y, val, defl) {
			self.out_lped(x, y, val, defl)
		},
	"8va":	out_8va,
	"8vb":	out_8vb,
	"15ma":	out_15ma,
	"15mb": out_15mb
}

function out_deco_val(x, y, name, val, defl) {
	if (deco_val_tb[name])
		deco_val_tb[name](x, y, val, defl)
	else
		error(1, null, "No function for decoration '$1'", name)
}

function out_glisq(x2, y2, de) {
    var	ar, a, len,
	de1 = de.start,
		x1 = de1.x,
		y1 = de1.y + staff_tb[de1.st].y,
		dx = x2 - x1,
		dy = self.sh(y1 - y2)

	if (!stv_g.g)
		dx /= stv_g.scale

	ar = Math.atan2(dy, dx)
	a = ar / Math.PI * 180
	len = (dx - (de1.s.dots ? 13 + de1.s.xmx : 8)
		- 8 - (de.s.notes[0].shac || 0))
			/ Math.cos(ar)

	g_open(x1, y1, a);
	x1 = de1.s.dots ? 13 + de1.s.xmx : 8;
	len = len / 6 | 0
	if (len < 1)
		len = 1
	while (--len >= 0) {
		xygl(x1, 0, "ltr");
		x1 += 6
	}
	g_close()
}

function out_gliss(x2, y2, de) {
    var	ar, a, len,
	de1 = de.start,
		x1 = de1.x,
		y1 = de1.y + staff_tb[de1.st].y,
		dx = x2 - x1,
		dy = self.sh(y1 - y2)

	if (!stv_g.g)
		dx /= stv_g.scale

	ar = Math.atan2(dy, dx)
	a = ar / Math.PI * 180
	len = (dx - (de1.s.dots ? 13 + de1.s.xmx : 8)
		- 8 - (de.s.notes[0].shac || 0))
			/ Math.cos(ar)

	g_open(x1, y1, a);
	xypath(de1.s.dots ? 13 + de1.s.xmx : 8, 0)
	output += 'h' + len.toFixed(1) + '" stroke-width="1"/>\n';
	g_close()
}

var deco_l_tb = {
	glisq: out_glisq,
	gliss: out_gliss
}

function out_deco_long(x, y, de) {
    var	s, p_v, m, nt, i,
	name = de.dd.glyph,
	de1 = de.start

	if (!deco_l_tb[name]) {
		error(1, null, "No function for decoration '$1'", name)
		return
	}

	// if no start or no end, get the y offset of the other end
	p_v = de.s.p_v				// voice
	if (de.defl.noen) {			// if no end
		s = p_v.s_next			// start of the next music line
		while (s && !s.dur)
			s = s.next
		if (s) {
			for (m = 0; m <= s.nhd; m++) {
				nt = s.notes[m]
				if (!nt.a_dd)
					continue
				for (i = 0; i < nt.a_dd.length; i++) {
					if (nt.a_dd[i].name == de.dd.name) {
						y = 3 * (nt.pit - 18)
							+ staff_tb[de.s.st].y
						break
					}
				}
			}
		}
		x += 8				// (there is no note width)
	} else if (de.defl.nost) {		// no start
		s = p_v.s_prev			// end of the previous music line
		while (s && !s.dur)
			s = s.prev
		if (s) {
			for (m = 0; m <= s.nhd; m++) {
				nt = s.notes[m]
				if (!nt.a_dd)
					continue
				for (i = 0; i < nt.a_dd.length; i++) {
					if (nt.a_dd[i].name == de1.dd.name) {
						de1.y = 3 * (nt.pit - 18)
						break
					}
				}
			}
		}
		de1.x -= 8			// (there is no note width)
	}
	deco_l_tb[name](x, y, de)
}

// add a tempo note in 'str' and return its number of characters
function tempo_note(str, s, dur, dy) {
    var	p,
	elts = identify_note(s, dur)

	switch (elts[0]) {		// head
	case C.OVAL:
		p = "\ueca2"
		break
	case C.EMPTY:
		p = "\ueca3"
		break
	default:
		switch (elts[2]) {	// flags
		case 2:
			p = "\ueca9"
			break
		case 1:
			p = "\ueca7"
			break
		default:
			p = "\ueca5"
			break
		}
		break
	}
	str.push('<tspan\nclass="' +
			font_class(cfmt.musicfont) +
		'" style="font-size:' +
		(gene.curfont.size * 1.3).toFixed(1) + 'px"' +
		dy + '>' +
		p + '</tspan>'
		+ (elts[1] ? '\u2009.' : ''))		// dot
	return elts[1] ? 2 : 1
} // tempo_note()

// build the tempo string
function tempo_build(s) {
    var	i, j, bx, p, wh, dy, h,
	w = 0,
	str = []

	if (s.tempo_str)	// already done
		return

	// the music font must be defined
	if (!cfmt.musicfont.used)
		get_font("music")

	set_font("tempo")
	h = gene.curfont.size
	if (s.tempo_str1) {
		str.push(s.tempo_str1)
		w += strwh(s.tempo_str1)[0]
	}
	if (s.tempo_notes) {
		dy = ' dy="-1"'			// notes a bit higher
		h *= 1.3
		for (i = 0; i < s.tempo_notes.length; i++) {
			j = tempo_note(str, s, s.tempo_notes[i], dy)
			w += j * gene.curfont.swfac
			dy = ''
		}
		str.push('<tspan dy="1">=</tspan>')
		w += cwidf('=')
		if (s.tempo_ca) {
			str.push(s.tempo_ca)
			w += strwh(s.tempo_ca)[0]
			j = s.tempo_ca.length + 1
		}
		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
			j = tempo_note(str, s, s.new_beat, ' dy="-1"')
			w += j * gene.curfont.swfac
			dy = 'y'
		}
	}
	if (s.tempo_str2) {
		if (dy)
			str.push('<tspan\n\tdy="1">' +
					s.tempo_str2 + '</tspan>')
		else
			str.push(s.tempo_str2)
		w += strwh(s.tempo_str2)[0]
	}

	// build the string
	s.tempo_str = str.join(' ')
	w += cwidf(' ') * (str.length - 1)
	s.tempo_wh = [w, h]
} // tempo_build()

// output a tempo
function writempo(s, x, y) {
    var	bh

	set_font("tempo")
	if (gene.curfont.box) {
		gene.curfont.box = false
		bh = s.tempo_wh[1] + 2
	}

//fixme: xy_str() cannot be used because <tspan> in s.tempo_str
//fixme: then there cannot be font changes by "$n" in the Q: texts
	output += '<text class="' + font_class(gene.curfont) +
		'" x="'
	out_sxsy(x, '" y="', y + gene.curfont.size * .22)
	output += '">' + s.tempo_str + '</text>\n'

	if (bh) {
		gene.curfont.box = true
		output += '<rect class="stroke" x="'
		out_sxsy(x - 2, '" y="', y + bh - 1)
		output += '" width="' + (s.tempo_wh[0] + 4).toFixed(1) +
			'" height="' + bh.toFixed(1) +
			'"/>\n'
	}

	// don't display anymore
	s.invis = true
} // writempo()

// update the vertical offset
function vskip(h) {
	posy += h
}

// clear the styles
function clr_sty() {
	font_style = ''
	if (cfmt.fullsvg) {
		defined_glyph = {}
		for (var i = 0; i < abc2svg.font_tb.length; i++)
			abc2svg.font_tb[i].used = 0 //false
		ff.used = 0 //false		// clear the font-face
	} else {
		style =
			fulldefs = ''
	}
} // clr_sty()

// create the SVG image of the block
function svg_flush() {
	if (multicol || !user.img_out || posy == 0)
		return

    var	i, font,
	fmt = tsnext ? tsnext.fmt : cfmt,
	w = Math.ceil((fmt.trimsvg || fmt.singleline == 1)
		? (cfmt.leftmargin + img.wx * cfmt.scale + cfmt.rightmargin + 2)
		: img.width),
	head = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1"\n\
	xmlns:xlink="http://www.w3.org/1999/xlink"\n\
	fill="currentColor" stroke-width=".7"',
	g = ''

	glout()

	if (cfmt.fgcolor)
		head += ' color="' + cfmt.fgcolor + '"'
	font = get_font("music")
	head += ' class="' + font_class(font) +
		' tune' + tunes.length + '"\n'	// tune index for play

	posy *= cfmt.scale
	if (user.imagesize != undefined)
		head += user.imagesize
	else
		head += ' width="' + w
			+ 'px" height="' + posy.toFixed(2) + 'px"'
	head += ' viewBox="0 0 ' + w + ' '
		+ posy.toFixed(2) + '">\n'
	head += fulldefs
	if (cfmt.bgcolor)
		head += '<rect width="100%" height="100%" fill="'
			+ cfmt.bgcolor + '"/>\n'

	if (style || font_style)
		head += '<style>' + font_style + style + '\n</style>\n'

	if (defs)
		head += '<defs>' + defs + '\n</defs>\n'

	// if %%pagescale != 1, do a global scale
	// (with a container: transform scale in <svg> does not work
	//	the same in all browsers)
	// the class is used to know that the container is global
	if (cfmt.scale != 1) {
		head += '<g class="g" transform="scale(' +
			cfmt.scale + ')">\n';
		g = '</g>\n'
	}

	if (psvg)			// if PostScript support
		psvg.ps_flush(true);	// + setg(0)

	// start a block if needed
	if (parse.state == 1 && user.page_format && !blkdiv)
		blkdiv = 1		// new tune
	if (blkdiv > 0) {
		user.img_out(blkdiv == 1 ?
			'<div class="nobrk">' :
			'<div class="nobrk newpage">')
		blkdiv = -1		// block started
	} else if (blkdiv < 0 && cfmt.splittune) {
		i = 1			// header and first music line
		blkdiv = 0
	}
	user.img_out(head + output + g + "</svg>");
	if (i)
		user.img_out("</div>")
	output = ""

	clr_sty()
	defs = '';
	posy = 0
	img.wx = 0			// space used between the margins
}

// mark the end of a <div> block
function blk_flush() {
	svg_flush()
	if (blkdiv < 0 && !parse.state) {
		user.img_out('</div>')
		blkdiv = 0
	}
}
Abc.prototype.blk_flush = blk_flush
// abc2svg - tune.js - tune generation
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

var	par_sy,		// current staff system for parse
	cur_sy,		// current staff system for generation
	voice_tb,
	curvoice,
	staves_found,
	vover,		// voice overlay
	tsfirst

/* apply the %%voice options of the current voice */
function voice_filter() {
    var	opt

	function vfilt(opts, opt) {
	    var	i,
		sel = new RegExp(opt)

		if (sel.test(curvoice.id)
		 || sel.test(curvoice.nm)) {
			for (i = 0; i < opts.length; i++)
				self.do_pscom(opts[i])
		}
	}

	// global
	if (parse.voice_opts)
	    for (opt in parse.voice_opts) {
		if (parse.voice_opts.hasOwnProperty(opt))
			vfilt(parse.voice_opts[opt], opt)
	}

	// tune
	if (parse.tune_v_opts)
	    for (opt in parse.tune_v_opts) {
		if (parse.tune_v_opts.hasOwnProperty(opt))
			vfilt(parse.tune_v_opts[opt], opt)
	}
}

/* -- link a ABC symbol into the current voice -- */
// if a voice is ignored (not in %%staves) don't link the symbol
//	but update the time for P: and Q:
function sym_link(s) {
    var	tim = curvoice.time

	if (!s.fname)
		set_ref(s)
    if (!curvoice.ignore) {
	s.prev = curvoice.last_sym
	if (curvoice.last_sym)
		curvoice.last_sym.next = s
	else
		curvoice.sym = s
    } else if (s.bar_type) {
		curvoice.last_bar = s
    }
	curvoice.last_sym = s
	s.v = curvoice.v;
	s.p_v = curvoice;
	s.st = curvoice.cst;
	s.time = tim
	if (s.dur && !s.grace)
		curvoice.time += s.dur;
	parse.ufmt = true
	s.fmt = cfmt				// global parameters
	s.pos = curvoice.pos
	if (curvoice.second)
		s.second = true
	if (curvoice.floating)
		s.floating = true
	if (curvoice.eoln) {
		s.soln = true
		curvoice.eoln = false
	}
}

/* -- add a new symbol in a voice -- */
function sym_add(p_voice, type) {
	var	s = {
			type:type,
			dur:0
		},
		s2,
		p_voice2 = curvoice;

	curvoice = p_voice;
	sym_link(s);
	curvoice = p_voice2;
	s2 = s.prev
	if (!s2)
		s2 = s.next
	if (s2) {
		s.fname = s2.fname;
		s.istart = s2.istart;
		s.iend = s2.iend
	}
	return s
}

/* -- sort all symbols by time and vertical sequence -- */
// weight of the symbols !! depends on the symbol type !!
var w_tb = new Uint8Array([
	6,	// bar
	2,	// clef
	8,	// custos
	6,	// sm (sequence marker, after bar)
	7,	// grace
	3,	// key
	4,	// meter
	9,	// mrest
	9,	// note
	0,	// part
	9,	// rest
	5,	// space (before bar)
	0,	// staves
	1,	// stbrk
	0,	// tempo
	0,	// (free)
	0,	// block
	0	// remark
])

function sort_all() {
    var	s, s2, time, w, wmin, ir, fmt, v, p_voice, prev,
	fl, new_sy,
	nv = voice_tb.length,
	vtb = [],
	vn = [],			// voice indexed by range
	sy = cur_sy			// first staff system

	// check if different bars at the same time
	function b_chk() {
	    var	bt, s, s2, v, t,
		ir = 0

		while (1) {
			v = vn[ir++]
			if (v == undefined)
				break
			s = vtb[v]
			if (!s || !s.bar_type || s.invis
			 || s.time != time)
				continue
			if (!bt) {
				bt = s.bar_type
				if (s.text && bt == '|')
					t = s.text
				continue
			}
			if (s.bar_type != bt)
				break
			if (s.text && !t && bt == '|') {
				t = s.text
				break
			}
		}

		if (v == undefined)
			return			// no problem

		// change "::" to ":| |:"
		// and    "|1" to "| [1"
		if (bt == "::" || bt == ":|"
		 || t) {
			ir = 0
			bt = t ? '|' : "::"
			while (1) {
				v = vn[ir++]
				if (v == undefined)
					break
				s = vtb[v]
				if (!s || s.invis
				 || s.bar_type != bt
				 || (bt == '|' && !s.text))
					continue
				s2 = clone(s)
				if (bt == "::") {
					s.bar_type = ":|"
					s2.bar_type = "|:"
				} else {
//					s.bar_type = '|'
					delete s.text
					delete s.rbstart
					s2.bar_type = '['
					s2.invis = 1 //true
					s2.xsh = 0
				}
				s2.next = s.next
				if (s2.next)
					s2.next.prev = s2
				s2.prev = s
				s.next = s2
			}
		} else {
			error(1, s, "Different bars $1 and $2",
				(bt + (t || '')), (s.bar_type + (s.text || '')))
		}
	} // b_chk()

	// set the first symbol of each voice
	for (v = 0; v < nv; v++) {
		s = voice_tb[v].sym
		vtb[v] = s
		if (sy.voices[v]) {
			vn[sy.voices[v].range] = v
			if (!prev && s) {
				fmt = s.fmt
				p_voice = voice_tb[v]
				prev = {	// symbol defining the first staff system
					type: C.STAVES,
					fname: parse.fname,
					dur: 0,
					v: v,
					p_v: p_voice,
					time: 0,
					st: 0,
					sy: sy,
					next: s,
					fmt: fmt,
					seqst: true
				}
			}
		}
	}

	if (!prev)
		return					// no symbol yet

	// insert the first staff system in the first voice
	p_voice.sym = tsfirst = s = prev
	if (s.next)
		s.next.prev = s
	else
		p_voice.last_sym = s

	// if Q: from tune header, put it at start of the music
	// (after the staff system)
	s = glovar.tempo
	if (s) {
		s.v = v = p_voice.v
		s.p_v = p_voice
		s.st = 0
		s.time = 0
		s.prev = prev
		s.next = prev.next
		if (s.next)
			s.next.prev = s
		else
			p_voice.last_sym = s
		s.prev.next = s
		s.fmt = fmt
		glovar.tempo = null
		vtb[v] = s
	}

	// if only one voice, quickly create the time links
	if (nv == 1) {
		s = tsfirst
		s.ts_next = s.next
		while (1) {
			s = s.next
			if (!s)
				return
			if (s.time != s.prev.time
			 || w_tb[s.prev.type])
				s.seqst = 1 //true
			if (s.type == C.PART) {		// move the part
				s.prev.next =
					s.prev.ts_next = s.next
				if (s.next) {
					s.next.part = s	// to the next symbol
					s.next.prev = s.prev
					if (s.soln)
						s.next.soln = 1 //true
					if (s.seqst)
						s.next.seqst = 1 //true
				}
				continue
			}
			s.ts_prev = s.prev
			s.ts_next = s.next
		}
		// not reached
	}

	// loop on the symbols of all voices
	while (1) {
		if (new_sy) {
			sy = new_sy;
			new_sy = null;
			vn.length = 0
			for (v = 0; v < nv; v++) {
				if (!sy.voices[v])
					continue
				vn[sy.voices[v].range] = v
			}
		}

		/* search the min time and symbol weight */
		wmin = time = 10000000		// big int
		ir = 0
		while (1) {
			v = vn[ir++]
			if (v == undefined)
				break
			s = vtb[v]
			if (!s || s.time > time)
				continue
			w = w_tb[s.type]
			if (s.type == C.GRACE
			 && s.next
			 && s.next.type == C.BAR)
				w = 5			// < bar
			if (s.time < time) {
				time = s.time;
				wmin = w
			} else if (w < wmin) {
				wmin = w
			}
		}

		if (wmin > 127)
			break			// done

		// check the type of the measure bars
		if (wmin == 6)			// !! weight of bars
			b_chk()

		/* link the vertical sequence */
		ir = 0
		while (1) {
			v = vn[ir++]
			if (v == undefined)
				break
			s = vtb[v]
			if (!s
			 || s.time != time)
				continue
			w = w_tb[s.type]
			if (s.type == C.GRACE
			 && s.next
			 && s.next.type == C.BAR)
				w = 5			// < bar
			if (w != wmin)
				continue
			if (!w
			 && s.type == C.PART) {		// move the part
				if (s.prev)
					s.prev.next = s.next
				else
					s.p_v.sym = s.next
				vtb[v] = s.next
				if (s.next) {
					s.next.part = s	// to the next symbol
					s.next.prev = s.prev
					if (s.soln)
						s.next.soln = 1 //true
//				} else {
// ignored
				}
				continue
			}
			if (s.type == C.STAVES)
				new_sy = s.sy
			if (fl) {
				fl = 0;
				s.seqst = true
			}
			s.ts_prev = prev
			prev.ts_next = s
			prev = s

			vtb[v] = s.next
		}
		if (wmin)			// if some width
			fl = 1 //true		// start a new sequence
	}
}

// adjust some voice elements
// (possible hook)
Abc.prototype.voice_adj = function (sys_chg) {
    var	p_voice, s, s2, v, sl

	// insert the delayed P: and Q: in the top_voice
	function ins_pq() {
	    var	s, s2,
		p_v = voice_tb[par_sy.top_voice]

		while (1) {
			s = parse.pq_d.shift()
			if (!s)
				break
			for (s2 = p_v.sym; ; s2 = s2.next) {
				if (s2.time >= s.time
				 && s2.dur) {
					s.next = s2
					s.prev = s2.prev
					s.prev.next =
						s2.prev = s
					s.v = s2.v
					s.p_v = p_v
					s.st = s2.st
					break
				}
			}
		}
	} // ins_pq()

	// set the duration of the notes under a feathered beam
	function set_feathered_beam(s1) {
		var	s, s2, t, d, b, i, a,
			d = s1.dur,
			n = 1

		/* search the end of the beam */
		for (s = s1; s; s = s.next) {
			if (s.beam_end || !s.next)
				break
			n++
		}
		if (n <= 1) {
			delete s1.feathered_beam
			return
		}
		s2 = s;
		b = d / 2;		/* smallest note duration */
		a = d / (n - 1);	/* delta duration */
		t = s1.time
		if (s1.feathered_beam > 0) {	/* !beam-accel! */
			for (s = s1, i = n - 1;
			     s != s2;
			     s = s.next, i--) {
				d = ((a * i) | 0) + b;
				s.dur = d;
				s.time = t;
				t += d
			}
		} else {				/* !beam-rall! */
			for (s = s1, i = 0;
			     s != s2;
			     s = s.next, i++) {
				d = ((a * i) | 0) + b;
				s.dur = d;
				s.time = t;
				t += d
			}
		}
		s.dur = s.time + s.dur - t;
		s.time = t
	} // end set_feathered_beam()

	// terminate voice cloning
	if (curvoice && curvoice.clone) {
		parse.istart = parse.eol
		do_cloning()
	}

	// if only one voice and a time skip,
	// fill the voice with the sequence "Z |" (multi-rest and bar)
	if (par_sy.one_v)			// if one voice
		fill_mr_ba(voice_tb[par_sy.top_voice])

	if (parse.pq_d)
		ins_pq()			// insert delayed P: and Q:

	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (!sys_chg) {			// if not %%score
			delete p_voice.eoln
			while (1) {		// set the end of slurs
				sl = p_voice.sls.shift()
				if (!sl)
					break
				s = sl.ss
//					error(1, s, "Lack of ending slur(s)")
					if (!s.sls)
						s.sls = []
				sl.loc = 'o'		// no slur end
				s.sls.push(sl)
			}
		} // not %%score
		for (s = p_voice.sym; s; s = s.next) {
			if (s.time >= staves_found)
				break
		}
		for ( ; s; s = s.next) {

			// if the symbol has a sequence weight smaller than the bar one
			// and if there a time skip,
			// add an invisible bar before it
			if (w_tb[s.type] < 5
			 && s.type != C.STAVES
			 && s.type != C.CLEF
			 && s.time			// not at start of tune
			 && (!s.prev || s.time > s.prev.time + s.prev.dur)) {
				s2 = {
					type: C.BAR,
					bar_type: "[]",
					v: s.v,
					p_v: s.p_v,
					st: s.st,
					time: s.time,
					dur:0,
					next: s,
					prev: s.prev,
					fmt: s.fmt,
					invis: 1
				}
				if (s.prev)
					s.prev.next = s2
				else
					voice_tb[s.v].sym = s2
				s.prev = s2
			}

			switch (s.type) {
			case C.GRACE:
				if (!cfmt.graceword)
					continue
				for (s2 = s.next; s2; s2 = s2.next) {
					switch (s2.type) {
					case C.SPACE:
						continue
					case C.NOTE:
						if (!s2.a_ly)
							break
						s.a_ly = s2.a_ly;
						s2.a_ly = null
						break
					}
					break
				}
				continue
			case C.NOTE:
				if (s.feathered_beam)
					set_feathered_beam(s)
				break
			}
		}
	}
}

/* -- create a new staff system -- */
function new_syst(init) {
    var	st, v, sy_staff, p_voice,
	sy_new = {
		voices: [],
		staves: [],
		top_voice: 0
	}

	if (init) {				/* first staff system */
		cur_sy = par_sy = sy_new
		return
	}

	// update the previous system
	for (v = 0; v < voice_tb.length; v++) {
	    if (par_sy.voices[v]) {
		st = par_sy.voices[v].st
		sy_staff = par_sy.staves[st]
		p_voice = voice_tb[v]

		sy_staff.staffnonote = p_voice.staffnonote
		if (p_voice.staffscale)
			sy_staff.staffscale = p_voice.staffscale;
	    }
	}
	for (st = 0; st < par_sy.staves.length; st++) {
		sy_new.staves[st] = clone(par_sy.staves[st]);
		sy_new.staves[st].flags = 0
	}
	par_sy.next = sy_new;
	par_sy = sy_new
}

/* -- set the bar numbers -- */
// (possible hook)
Abc.prototype.set_bar_num = function() {
    var	s, s2, rep_tim, k, n, nu, txt,
	tim = 0,			// time of the previous bar
	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

	// check the measure duration
	function check_meas() {
	    var	s3

		if (tim > ptim + wmeasure
		 && s.prev.type != C.MREST)
			return 1 //true

		// the measure is too short,
		// check if there is a bar a bit further
		for (s3 = s.next; s3 && s3.time == s.time; s3 = s3.next)
			;
		for ( ; s3 && !s3.bar_type; s3 = s3.next)
			;
		return s3 && (s3.time - bar_tim) % wmeasure
	}

	// 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)
				bar_num = s.bar_num	// %%setbarnb)
			break
		}
		break
	}

	// at start of tune, check for an anacrusis
	for (s2 = s.ts_next; s2; s2 = s2.ts_next) {
		if (s2.type == C.BAR && s2.time
		 && !s2.invis && !s2.bar_dotted) {
			if (s2.time < wmeasure) {	// if anacrusis
				s = s2
				bar_tim = s.time
			}
			break
		}
	}

	// set the measure number on the top bars
	for ( ; s; s = s.ts_next) {
		switch (s.type) {
		case C.METER:
			if (wmeasure != 1)		// if not M:none
				bar_num += (s.time - bar_tim) / wmeasure
			bar_tim = s.time
			wmeasure = s.wmeasure
			while (s.ts_next && s.ts_next.wmeasure)
				s = s.ts_next
			break
		case C.BAR:
			if (s.time <= tim)
				break			// already seen
			tim = s.time

			nu = 1 //true			// no num update
			txt = ""
			for (s2 = s; s2; s2 = s2.next) {
				if (s2.time > tim)
					break
				if (!s2.bar_type)
					continue
				if (s2.bar_type != '[')
					nu = 0 //false	// do update
				if (s2.text)
					txt = s2.text
			}
			if (s.bar_num) {
				bar_num = s.bar_num	// (%%setbarnb)
				ptim = bar_tim = tim
				break
			}
			if (wmeasure == 1) {		// if M:none
				if (s.bar_dotted)
					break
				if (txt) {
					if (!cfmt.contbarnb) {
						if (txt[0] == '1')
							rep_tim = bar_num
						else
							bar_num = rep_tim
					}
				}
				if (!nu)
					s.bar_num = ++bar_num
				break
			}

			n = bar_num + (tim - bar_tim) / wmeasure
			k = n - (n | 0)
			if (cfmt.checkbars
			 && k
			 && check_meas())
				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 (txt) {
				if (txt[0] == '1') {
					if (!cfmt.contbarnb)
						rep_tim = tim - bar_tim
					if (!nu)
						s.bar_num = n
				} else {
					if (!cfmt.contbarnb)
						bar_tim = tim - rep_tim
					n = bar_num + (tim - bar_tim) / wmeasure
					if (n == (n | 0))
						s.bar_num = n
				}
			} else if (n == (n | 0)) {
				s.bar_num = n
			}
			if (!k)
				ptim = tim
			break
		}
	}
}

// convert a note to ABC
function not2abc(pit, acc) {
    var	i,
	nn = ''

	if (acc) {
		if (typeof acc != "object") {
			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 >= 23; i -= 7)
		nn += "'"
	for (i = pit; i < 16; 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 = text.split(/\s+/)

	if (a.length < 3) {
		syntax(1, errs.not_enough_p)
		return
	}
	ns = a[1]
	if (ns != '*') {
 		if (ns.indexOf("octave,") == 0	// remove the octave part
		 || ns.indexOf("key,") == 0
		 || !ns.indexOf("tonic,")) {
			ty = ns[0]
			ns = ns.split(',')[1].toUpperCase()
		}
		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 = {}
	map = notes[ns]
	if (!map)
		notes[ns] = map = []

	// try the optional 'print' and 'heads' parameters
	a.shift()
	a.shift()
	if (!a.length)
		return
	a = info_split(a.join(' '))
	i = 0
	if (a[0].indexOf('=') < 0) {
		if (a[0][0] != '*') {
			tmp = new scanBuf;		// print
			tmp.buffer = a[0];
			map[1] = parse_acc_pit(tmp)
		}
		if (!a[1])
			return
		i++
		if (a[1].indexOf('=') < 0) {
			map[0] = a[1].split(',')	// heads
			i++
		}
	}

	for (; i < a.length; i++) {
		switch (a[i]) {
		case "heads=":
			if (!a[++i]) {
				syntax(1, errs.not_enough_p)
				break
			}
			map[0] = a[i].split(',')
			break
		case "print=":
		case "play=":
		case "print_notrp=":
			if (!a[++i]) {
				syntax(1, errs.not_enough_p)
				break
			}
			tmp = new scanBuf;
			tmp.buffer = a[i];
			note = parse_acc_pit(tmp)
			if (a[i - 1][5] == '_')		// if print no transpose
				note.notrp = 1 //true
			if (a[i - 1][1] == 'r')
				map[1] = note
			else
				map[3] = note
			break
		case "color=":
			if (!a[++i]) {
				syntax(1, errs.not_enough_p)
				break
			}
			map[2] = a[i]
			break
		}
	}
}

// get a abcm2ps/abcMIDI compatible transposition value as a base-40 interval
// The value may be
// - [+|-]<number of semitones>[s|f]
// - <note1>[<note2>]  % <note2> default is 'c'
function get_transp(param) {
	if (param[0] == '0')
		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, errs.bad_transp)
			return
		}
		val += 36
		val = ((val / 12 | 0) - 3) * 40 + abc2svg.isb40[val % 12]
		if (param.slice(-1) == 'b')
			val += 4
		return val
	}
	// return undefined
} // get_transp()

/* -- process a pseudo-comment (%% or I:) -- */
// (possible hook)
Abc.prototype.do_pscom = function(text) {
    var	h1, val, s, cmd, param, n, k, b

	cmd = text.match(/[^\s]+/)
	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
	}

	switch (cmd) {
	case "clef":
		if (parse.state >= 2) {
			s = new_clef(param)
			if (s)
				get_clef(s)
		}
		return
	case "deco":
		deco_add(param)
		return
	case "linebreak":
		set_linebreak(param)
		return
	case "map":
		get_map(param)
		return
	case "maxsysstaffsep":
	case "sysstaffsep":
		if (parse.state == 3) {
			val = get_unit(param)
			if (isNaN(val)) {
				syntax(1, errs.bad_val, "%%" + cmd)
				return
			}
			par_sy.voices[curvoice.v][cmd[0] == 'm' ? "maxsep" : "sep"] =
					val
			return
		}
		break
	case "multicol":
		switch (param) {
		case "start":
		case "new":
		case "end":
			break
		default:
			syntax(1, "Unknown keyword '$1' in %%multicol", param)
			return
		}
		s = {
			type: C.BLOCK,
			subtype: "mc_" + param,
			dur: 0
		}
		if (parse.state >= 2) {
			if (curvoice.clone)
				do_cloning()
			curvoice = voice_tb[0]
			curvoice.eoln = 1 //true
			sym_link(s)
			return
		}
		set_ref(s)
		self.block_gen(s)
		return
	case "ottava":
		if (parse.state != 3)
			return
		n = parseInt(param)
		if (isNaN(n) || n < -2 || n > 2
		 || (!n && !curvoice.ottava)) {
			syntax(1, errs.bad_val, "%%ottava")
			return
		}
		k = n
		if (n) {
			curvoice.ottava = n
		} else {
			n = curvoice.ottava
			curvoice.ottava = 0
		}
		a_dcn.push(["15mb", "8vb", "", "8va", "15ma"][n + 2]
			+ (k ? '(' : ')'))
		return
	case "repbra":
		if (curvoice)
			curvoice.norepbra = !get_bool(param)
		return
	case "repeat":
		if (parse.state != 3)
			return
		if (!curvoice.last_sym) {
			syntax(1, "%%repeat cannot start a tune")
			return
		}
		if (!param.length) {
			n = 1;
			k = 1
		} else {
			b = param.split(/\s+/);
			n = parseInt(b[0]);
			k = parseInt(b[1])
			if (isNaN(n) || n < 1
			 || (curvoice.last_sym.type == C.BAR
			  && n > 2)) {
				syntax(1, "Incorrect 1st value in %%repeat")
				return
			}
			if (isNaN(k)) {
				k = 1
			} else {
				if (k < 1) {
					syntax(1, "Incorrect 2nd value in %%repeat")
					return
				}
			}
		}
		parse.repeat_n = curvoice.last_sym.type == C.BAR ? n : -n;
		parse.repeat_k = k
		return
	case "sep":
		var	h2, len, values, lwidth;

		set_page();
		lwidth = img.width - img.lm - img.rm;
		h1 = h2 = len = 0
		if (param) {
			values = param.split(/\s+/);
			h1 = get_unit(values[0])
			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 >= 2) {
			if (curvoice.clone)
				do_cloning()
			s = new_block(cmd);
			s.x = (lwidth - len) / 2 / cfmt.scale;
			s.l = len / cfmt.scale;
			s.sk1 = h1;
			s.sk2 = h2
			return
		}
		vskip(h1);
		output += '<path class="stroke"\n\td="M';
		out_sxsy((lwidth - len) / 2 / cfmt.scale, ' ', 0);
		output += 'h' + (len / cfmt.scale).toFixed(1) + '"/>\n';
		vskip(h2);
		blk_flush()
		return
	case "setbarnb":
		val = parseInt(param)
		if (isNaN(val) || val < 1) {
			syntax(1, "Bad %%setbarnb value")
			break
		}
		glovar.new_nbar = val
		return
	case "staff":
		if (parse.state != 3)
			return
		if (curvoice.clone)
			do_cloning()
		val = parseInt(param)
		if (isNaN(val)) {
			syntax(1, "Bad %%staff value '$1'", param)
			return
		}
		var st
		if (param[0] == '+' || param[0] == '-')
			st = curvoice.cst + val
		else
			st = val - 1
		if (st < 0 || st > nstaff) {
			syntax(1, "Bad %%staff number $1 (cur $2, max $3)",
					st, curvoice.cst, nstaff)
			return
		}
		delete curvoice.floating;
		curvoice.cst = st
		return
	case "staffbreak":
		if (parse.state != 3)
			return
		if (curvoice.clone)
			do_cloning()
		s = {
			type: C.STBRK,
			dur:0
		}
		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
		}
		sym_link(s)
		return
	case "tacet":
		if (param[0] == '"')
			param = param.slice(1, -1)
		// fall thru
	case "stafflines":
	case "staffscale":
	case "staffnonote":
		set_v_param(cmd, param)
		return
	case "staves":
	case "score":
		if (!parse.state)
			return
		if (parse.scores && parse.scores.length > 0) {
			text = parse.scores.shift();
			cmd = text.match(/([^\s]+)\s*(.*)/);
			param = cmd[2]
			cmd = cmd[1]
		}
		get_staves(cmd, param)
		return
	case "center":
	case "text":
		k = cmd[0] == 'c' ? 'c' : cfmt.textoption
		set_font("text")
		if (parse.state >= 2) {
			if (curvoice.clone)
				do_cloning()
			s = new_block("text")
			s.text = param
			s.opt = k
			s.font = cfmt.textfont
			return
		}
		write_text(param, k)
		return
	case "transpose":		// (abcm2ps compatibility)
		if (cfmt.sound)
			return
		val = get_transp(param)
		if (val == undefined) {		// accept note interval
			val = get_interval(param)
			if (val == undefined)
				return
		}
		switch (parse.state) {
		case 0:
			cfmt.transp = 0
			// fall thru
		case 1:
			cfmt.transp = (cfmt.transp || 0) + val
			return
		}
		curvoice.shift = val
		key_trans()
		return
	case "tune":
//fixme: to do
		return
	case "user":
		set_user(param)
		return
	case "voicecolor":
		if (curvoice)
			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 >= 2) {
			if (curvoice.clone)
				do_cloning()
			s = new_block(cmd);
			s.sk = val
			return
		}
		vskip(val);
		return
	case "newpage":
	case "leftmargin":
	case "rightmargin":
	case "pagescale":
	case "pagewidth":
	case "printmargin":
	case "scale":
	case "staffwidth":
		if (parse.state >= 2) {
			if (curvoice.clone)
				do_cloning()
			s = new_block(cmd);
			s.param = param
			return
		}
		if (cmd == "newpage") {
			blk_flush()
			if (user.page_format)
				blkdiv = 2	// start the next SVG in a new page
			return
		}
		break
	}
	self.set_format(cmd, param)
}

// treat the %%beginxxx / %%endxxx sequences
// (possible hook)
Abc.prototype.do_begin_end = function(type,
			opt,
			text) {
	var i, j, action, s

	if (curvoice && curvoice.clone)
		do_cloning()
	switch (type) {
	case "js":
		js_inject(text)
		break
	case "ml":
		if (cfmt.pageheight) {
			syntax(1, "Cannot have %%beginml with %%pageheight")
			break
		}
		if (parse.state >= 2) {
			s = new_block(type);
			s.text = text
		} else {
			blk_flush()
			if (user.img_out)
				user.img_out(text)
		}
		break
	case "svg":
		j = 0
		while (1) {
			i = text.indexOf('<style', j)
			if (i < 0)
				break
			i = text.indexOf('>', i)
			j = text.indexOf('</style>', i)
			if (j < 0) {
				syntax(1, "No </style> in %%beginsvg sequence")
				break
			}
			s = text.slice(i + 1, j).replace(/\s+$/gm, '')
			if (cfmt.fullsvg) {
				i = s.match(/@font-face[^}]*}/)
				if (i && i[0].indexOf("text") > 0) {
					ff.text = "\n"
						+ i[0]	// assume only one @font-face
					s = s.replace(i[0], '')
				}
			}
			if (s && s != "\n")
				style += s
		}
		j = 0
		while (1) {
			i = text.indexOf('<defs>\n', j)
			if (i < 0)
				break
			j = text.indexOf('</defs>', i)
			if (j < 0) {
				syntax(1, "No </defs> in %%beginsvg sequence")
				break
			}
			defs_add(text.slice(i + 6, j))
		}
		break
	case "text":
		action = get_textopt(opt);
		if (!action)
			action = cfmt.textoption
		set_font("text")
		if (text.indexOf('\\') >= 0)
			text = cnv_escape(text)
		if (parse.state > 1) {
			s = new_block(type);
			s.text = text
			s.opt = action
			s.font = cfmt.textfont
			break
		}
		write_text(text, action)
		break
	}
}

/* -- generate a piece of tune -- */
function generate() {
    var s, v, p_voice;

	if (a_dcn.length) {
		syntax(1, "Decoration(s) without symbol: $1", a_dcn)
		a_dcn = []
	}

	if (parse.tp) {
		syntax(1, "No end of tuplet")
		s = parse.tps
		if (s)
			delete s.tp
		delete parse.tp
	}

	if (vover) {
		syntax(1, "No end of voice overlay");
		get_vover(vover.bar ? '|' : ')')
	}

	self.voice_adj()
	sort_all()			/* define the time / vertical sequences */

    if (tsfirst) {
	for (v = 0; v < voice_tb.length; v++) {
		if (!voice_tb[v].key)
			voice_tb[v].key = parse.ckey	// set the starting key
	}
	if (user.anno_start)
		anno_start = a_start
	if (user.anno_stop)
		anno_stop = a_stop
	self.set_bar_num()

	if (info.P)
		tsfirst.parts = info.P	// for play

	// give the parser result to the application
	if (user.get_abcmodel)
		user.get_abcmodel(tsfirst, voice_tb, abc2svg.sym_name, info)

	if (user.img_out)		// if SVG generation
		self.output_music()
    } // (tsfirst)

	// finish the generation
	set_page()			// the page layout may have changed
	if (info.W)
		put_words(info.W)
	put_history()
	parse.state = 0			// file header
	blk_flush()			// (force end of block)

	if (tsfirst) {		// if non void, keep tune data for upper layers
		tunes.push([tsfirst, voice_tb, info, cfmt])
		tsfirst = null
	}
}

// transpose the current key of the voice (called on K: or V:)
function key_trans() {
    var	i, n, a_acc, b40, d,
	s = curvoice.ckey,			// current key
	ti = s.time || 0

	if (s.k_bagpipe || s.k_drum)
		return				// no transposition

	// set the score transposition
	n = (curvoice.score | 0)		// new transposition
		+ (curvoice.shift | 0)
		+ (cfmt.transp | 0)
	if ((curvoice.tr_sco | 0) == n) {	// if same transposition
		s.k_sf = curvoice.ckey.k_sf
		return
	}

	// get the current key or create a new one
	if (is_voice_sig()) {			// if no symbol yet
		curvoice.key = s		// new root key of the voice
	} else if (curvoice.time != ti) {	// if no K: at this time
		s = clone(s.orig || s)		// new key
		if (!curvoice.new)
			s.k_old_sf = curvoice.ckey.k_sf
		sym_link(s)
	}
	curvoice.ckey = s			// current key

	if (cfmt.transp && curvoice.shift)	// if %%transpose and shift=
		syntax(0, "Mix of old and new transposition syntaxes");

	// define the new key
	curvoice.tr_sco = n			// b40 interval

	n = abc2svg.b40l5[(n + 202) % 40]	// transpose in the line of fifth
		+ s.orig.k_sf			// + old = new sf
	if (n < -7) {
		n += 12
		curvoice.tr_sco -= 4
	} else if (n > 7) {
		n -= 12
		curvoice.tr_sco += 4
	}
	if (!s.k_none)
		s.k_sf = n
	for (b40 = 0; b40 < 40; b40++) {
		if (abc2svg.b40l5[b40] == n)
			break
	}
	s.k_b40 = b40

	// transpose the accidental list
	if (!s.k_a_acc)
		return
	d = b40 - s.orig.k_b40
	a_acc = []
	for (i = 0; i < s.k_a_acc.length; i++) {
		b40 = abc2svg.pab40(s.k_a_acc[i].pit, s.k_a_acc[i].acc) + d
		a_acc[i] = {
			pit: abc2svg.b40p(b40),
			acc: abc2svg.b40a(b40) || 3
		}
	}
	s.k_a_acc = a_acc
}

// fill a voice with a multi-rest and a bar
function fill_mr_ba(p_v) {
    var	v, p_v2,
	mxt = 0

	for (v = 0; v < voice_tb.length; v++) {
		if (voice_tb[v].time > mxt) {
			p_v2 = voice_tb[v]
			mxt = p_v2.time
		}
	}
	if (p_v.time >= mxt)
		return

    var	p_v_sav = curvoice,
	dur = mxt - p_v.time,
	s = {
		type: C.MREST,
		stem: 0,
		multi: 0,
		nhd: 0,
		xmx: 0,
		frm: 1, //true			// full measure rest
		dur: dur,
		dur_orig: dur,
		nmes: dur / p_v.wmeasure,
		notes: [{
			pit: 18,
			dur: dur
		}],
		tacet: p_v.tacet
	},
	s2 = {
		type: C.BAR,
		bar_type: '|',
		dur: 0,
		multi: 0
	}

	if (p_v2.last_sym.bar_type)
		s2.bar_type = p_v2.last_sym.bar_type
//	s2.soln = p_v2.last_sym.soln

	glovar.mrest_p = 1 //true

	curvoice = p_v
	sym_link(s)
	sym_link(s2)

	curvoice = p_v_sav
} // fill_mr_ba()

/* -- get staves definition (%%staves / %%score) -- */
function get_staves(cmd, parm) {
    var	s, p_voice, p_voice2, i, flags, v, vid, a_vf, eoln,
	st, range,
	nv = voice_tb.length,
	maxtime = 0

	// if sequence with many voices, load the other voices
	if (curvoice && curvoice.clone) {
//		i = parse.eol
//		parse.eol = parse.bol		// remove the %%staves line
		do_cloning()
//		parse.eol = i
	}

	if (parm) {
		a_vf = parse_staves(parm)	// => array of [vid, flags]
		if (!a_vf)
			return
	} else if (staves_found < 0) {
		syntax(1, errs.bad_val, '%%' + cmd)
		return
	}

	/* create a new staff system */
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		if (p_voice.eoln) {
			eoln = 1
			delete p_voice.eoln
		}
		if (p_voice.time > maxtime)
			maxtime = p_voice.time
	}
	if (!maxtime) {				// if first %%staves
		par_sy.staves = []
		par_sy.voices = []
	} else {
//		if (nv)					// if many voices
		self.voice_adj(1)

		// synchronize the voices
		for (v = 0; v < nv; v++) {
			p_voice = voice_tb[v]
//fixme: does not work if measure bar and %%staves delta time < measure duration
			if (maxtime - p_voice.time >= p_voice.meter.wmeasure)
				p_voice.acc = []	// no accidental anymore
			p_voice.time = maxtime
			p_voice.lyric_restart = p_voice.last_sym
			p_voice.sym_restart = p_voice.last_sym
		}

		/*
		 * create a new staff system and
		 * link the 'staves' symbol in a voice which is seen from
		 * the previous system - see sort_all
		 */
	   if (!par_sy.voices[curvoice.v])
		for (v = 0; v < par_sy.voices.length; v++) {
			if (par_sy.voices[v]) {
				curvoice = voice_tb[v]
				break
			}
		}

		curvoice.eoln = eoln
		s = {
			type: C.STAVES,
			dur: 0
		}

		sym_link(s);		// link the staves in this voice
		par_sy.nstaff = nstaff;

		// if no parameter, duplicate the current staff system
		if (!parm) {
			s.sy = clone(par_sy, 2)		// clone the staves and voices
			par_sy.next = s.sy
			par_sy = s.sy
			staves_found = maxtime
			curvoice = voice_tb[par_sy.top_voice]
			return
		}

		new_syst();
		s.sy = par_sy
	}

	staves_found = maxtime

	/* initialize the (old) voices */
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		delete p_voice.second
		delete p_voice.floating
		if (p_voice.ignore) {
			p_voice.ignore = 0 //false
			s = p_voice.sym
			if (s) {
				while (s.next)
					s = s.next
			}
			p_voice.last_sym = s	// set back the last symbol
		}
	}
	range = 0
	for (i = 0; i < a_vf.length; i++) {
		vid = a_vf[i][0];
		p_voice = new_voice(vid);
		v = p_voice.v

		a_vf[i][0] = p_voice;

		// set the range and add the overlay voices
		while (1) {
			par_sy.voices[v] = {
				range: range++
			}
			p_voice = p_voice.voice_down
			if (!p_voice)
				break
			v = p_voice.v
		}
	}
	par_sy.top_voice = a_vf[0][0].v
	if (a_vf.length == 1)
		par_sy.one_v = 1 //true			// one voice

	/* change the behavior from %%staves to %%score */
	if (cmd[1] == 't') {				/* if %%staves */
		for (i = 0; i < a_vf.length; i++) {
			flags = a_vf[i][1]
			if (!(flags & (OPEN_BRACE | OPEN_BRACE2)))
				continue
			if ((flags & (OPEN_BRACE | CLOSE_BRACE))
					== (OPEN_BRACE | CLOSE_BRACE)
			 || (flags & (OPEN_BRACE2 | CLOSE_BRACE2))
					== (OPEN_BRACE2 | CLOSE_BRACE2))
				continue
			if (a_vf[i + 1][1] != 0)
				continue
			if ((flags & OPEN_PARENTH)
			 || (a_vf[i + 2][1] & OPEN_PARENTH))
				continue

			/* {a b c} -> {a *b c} */
			if (a_vf[i + 2][1] & (CLOSE_BRACE | CLOSE_BRACE2)) {
				a_vf[i + 1][1] |= FL_VOICE

			/* {a b c d} -> {(a b) (c d)} */
			} else if (a_vf[i + 2][1] == 0
				&& (a_vf[i + 3][1]
					& (CLOSE_BRACE | CLOSE_BRACE2))) {
				a_vf[i][1] |= OPEN_PARENTH;
				a_vf[i + 1][1] |= CLOSE_PARENTH;
				a_vf[i + 2][1] |= OPEN_PARENTH;
				a_vf[i + 3][1] |= CLOSE_PARENTH
			}
		}
	}

	/* set the staff system */
	st = -1
	for (i = 0; i < a_vf.length; i++) {
		flags = a_vf[i][1]
		if ((flags & (OPEN_PARENTH | CLOSE_PARENTH))
				== (OPEN_PARENTH | CLOSE_PARENTH)) {
			flags &= ~(OPEN_PARENTH | CLOSE_PARENTH);
			a_vf[i][1] = flags
		}
		p_voice = a_vf[i][0]
		if (flags & FL_VOICE) {
			p_voice.floating = true;
			p_voice.second = true
		} else {
			st++;
			if (!par_sy.staves[st]) {
				par_sy.staves[st] = {
					staffscale: 1
				}
			}
			par_sy.staves[st].stafflines = p_voice.stafflines || "|||||",
			par_sy.staves[st].flags = 0
		}
		v = p_voice.v;
		p_voice.st = p_voice.cst =
				par_sy.voices[v].st = st;
		par_sy.staves[st].flags |= flags
		if (flags & OPEN_PARENTH) {
			p_voice2 = p_voice
			while (i < a_vf.length - 1) {
				p_voice = a_vf[++i][0];
				v = p_voice.v
				if (a_vf[i][1] & MASTER_VOICE) {
					p_voice2.second = true
					p_voice2 = p_voice
				} else {
					p_voice.second = true;
				}
				p_voice.st = p_voice.cst
						= par_sy.voices[v].st
						= st
				if (a_vf[i][1] & CLOSE_PARENTH)
					break
			}
			par_sy.staves[st].flags |= a_vf[i][1]
		}
	}
	if (st < 0)
		st = 0
	par_sy.nstaff = nstaff = st

	/* change the behaviour of '|' in %%score */
	if (cmd[1] == 'c') {				/* if %%score */
		for (st = 0; st < nstaff; st++)
			par_sy.staves[st].flags ^= STOP_BAR
	}

	nv = voice_tb.length
	st = 0
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		if (par_sy.voices[v])
			st = p_voice.st
		else
			p_voice.st = st	// (this avoids later crashes)

		// if first %%staves
		// update the staff of the symbols with no time
		if (!maxtime) {
			for (s = p_voice.sym; s; s = s.next)
				s.st = st
		}

		if (!par_sy.voices[v])
			continue

		// set the staff of the overlay voices
		p_voice2 = p_voice.voice_down
		while (p_voice2) {
			p_voice2.second = 1 //true
			i = p_voice2.v
			p_voice2.st = p_voice2.cst =
					par_sy.voices[i].st = st
			p_voice2 = p_voice2.voice_down
		}

		par_sy.voices[v].second = p_voice.second;
		st = p_voice.st
		if (st > 0 && p_voice.norepbra == undefined
		 && !(par_sy.staves[st - 1].flags & STOP_BAR))
			p_voice.norepbra = true
	}

	curvoice = parse.state >= 2 ? voice_tb[par_sy.top_voice] : null
}

	// get a voice or create a clone of the current voice
	function clone_voice(id) {
		var v, p_voice

		for (v = 0; v < voice_tb.length; v++) {
			p_voice = voice_tb[v]
			if (p_voice.id == id)
				return p_voice		// found
		}
		p_voice = clone(curvoice);
		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
		delete p_voice.lyric_cont
		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()

/* -- get a voice overlay -- */
function get_vover(type) {
    var	p_voice2, p_voice3, range, s, time, v, v2, v3, s2

	/* treat the end of overlay */
	if (type == '|'
	 || type == ')')  {
		if (!curvoice.last_note) {
			syntax(1, errs.nonote_vo)
			if (vover) {
				curvoice = vover.p_voice
				vover = null
			}
			return
		}
		curvoice.last_note.beam_end = true
		if (!vover) {
			syntax(1, "Erroneous end of voice overlay")
			return
		}
		if (curvoice.time != vover.p_voice.time) {
		    if (!curvoice.ignore)
			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

		// if the last symbols are spaces, move them to the main voice
		p_voice2 = vover.p_voice	// main voice
		s = curvoice.last_sym
		if (s.type == C.SPACE && p_voice2.last_sym.type != C.SPACE) {
			s.p_v = p_voice2
			s.v = s.p_v.v
			while (s.prev.type == C.SPACE) {
				s = s.prev
				s.p_v = p_voice2
				s.v = s.p_v.v
			}
			s2 = s.prev
			s2.next = null
			s.prev = p_voice2.last_sym
			s.prev.next = s
			p_voice2.last_sym = curvoice.last_sym
			curvoice.last_sym = s2
		}

		curvoice = p_voice2
		vover = null
		return
	}

	/* treat the full overlay start */
	if (type == '(') {
		if (vover) {
			syntax(1, "Voice overlay already started")
			return
		}
		vover = {
			p_voice: curvoice,
			time: curvoice.time
		}
		return
	}

	/* (here is treated a new overlay - '&') */
	/* create the extra voice if not done yet */
	if (!curvoice.last_note) {
		syntax(1, errs.nonote_vo)
		return
	}
	curvoice.last_note.beam_end = true;
	p_voice2 = curvoice.voice_down
	if (!p_voice2) {
		p_voice2 = clone_voice(curvoice.id + 'o');
		curvoice.voice_down = p_voice2;
		p_voice2.time = 0;
		p_voice2.second = true;
		p_voice2.last_note = null
		v2 = p_voice2.v;
	    if (par_sy.voices[curvoice.v]) {	// if voice in the staff system
		par_sy.voices[v2] = {
			st: curvoice.st,
			second: true
		}
		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++
		}
		par_sy.voices[v2].range = range + 1
	    }
	}
	p_voice2.ulen = curvoice.ulen
	p_voice2.dur_fact = curvoice.dur_fact
	p_voice2.acc = []			// no accidental

	if (!vover) {				/* first '&' in a measure */
		time = p_voice2.time
	    if (curvoice.ignore)
		s = curvoice.last_bar
	    else
		for (s = curvoice.last_sym; s; s = s.prev) {
			if (s.type == C.BAR
			 || s.time <= time)	/* (if start of tune) */
				break
		}
		vover = {
			bar: (s && s.bar_type) ? s.bar_type : '|',
			p_voice: curvoice,
			time: s ? s.time : curvoice.time
		}
	} else {
		if (curvoice != vover.p_voice
		 && curvoice.time != vover.p_voice.time) {
			syntax(1, "Wrong duration in voice overlay")
			if (curvoice.time > vover.p_voice.time)
				vover.p_voice.time = curvoice.time
		}
	}
	p_voice2.time = vover.time;
	curvoice = p_voice2
}

// check if a clef, key or time signature may go at start of the current voice
function is_voice_sig() {
	var s

	if (curvoice.time)
		return false
	if (!curvoice.last_sym)
		return true
	for (s = curvoice.last_sym; s; s = s.prev)
		if (w_tb[s.type])
			return false
	return true
}

// treat a clef found in the tune body
function get_clef(s) {
    var	s2, s3

	// special case for percussion
	if (s.clef_type == 'p') {		// if percussion clef
		s2 = curvoice.ckey
		s2.k_drum = 1 //true
		s2.k_sf = 0
		s2.k_b40 = 2
		s2.k_map = abc2svg.keys[7]
		if (!curvoice.key)
			curvoice.key = s2	// new root key
	}

	if (!curvoice.time		// (force a clef when new voice)
	 && is_voice_sig()) {
		curvoice.clef = s
		s.fmt = cfmt
		return
	}

	// if not clef=none,
	// move the clef before a key and/or a (not right repeat) bar
    if (s.clef_none)
	s2 = null
    else
	for (s2 = curvoice.last_sym;
	     s2 && s2.time == curvoice.time;
	     s2 = s2.prev) {
		if (w_tb[s2.type])
			break
	}
	if (s2
	 && s2.time == curvoice.time		// if no time skip
	 && s2.k_sf != undefined) {
		s3 = s2				// move before a key signature
		s2 = s2.prev
	}
	if (s2
	 && s2.time == curvoice.time
	 && s2.bar_type && s2.bar_type[0] != ':')
		s3 = s2				// move before a measure bar
	if (s3) {
		s2 = curvoice.last_sym
		curvoice.last_sym = s3.prev
		sym_link(s)
		s.next = s3
		s3.prev = s
		curvoice.last_sym = s2
		if (s.soln) {
			delete s.soln
			curvoice.eoln = true
		}
	} else {
		sym_link(s)
	}
}

// treat K: (kp = key signature + parameters)
function get_key(parm) {
    var	v, p_voice,
//		[s_key, a] = new_key(parm)	// KO with nodejs
		a = new_key(parm),
		s_key = a[0],
		s = s_key,
		empty = s.k_sf == undefined && !s.k_a_acc

	a = a[1]

	if (empty)
		s.invis = 1 //true		// don't display empty K:
	else
		s.orig = s			// new transposition base

	if (parse.state == 1) {			// in tune header (first K:)
		parse.ckey = s			// root key
		if (empty) {
			s_key.k_sf = 0;
			s_key.k_none = true
			s_key.k_map = abc2svg.keys[7]
		}
		for (v = 0; v < voice_tb.length; v++) {
			p_voice = voice_tb[v];
			p_voice.ckey = clone(s_key)
		}
		if (a.length) {
			memo_kv_parm('*', a)
			a = []
		}
		if (!glovar.ulen)
			glovar.ulen = C.BLEN / 8;
		goto_tune()
	} else if (!empty) {
		if (curvoice.tr_sco)
			curvoice.tr_sco = undefined
		s.k_old_sf = curvoice.ckey.k_sf	// memorize the previous key
		curvoice.ckey = s
		sym_link(s)
	}

	// set the voice parameters
	if (!curvoice) {			// if first K:
		if (!voice_tb.length) {
			curvoice = new_voice("1")
		    var	def = 1 // true
		} else {
			curvoice = voice_tb[staves_found < 0 ? 0 : par_sy.top_voice]
		}
	}

	p_voice = curvoice.clone
	if (p_voice)
		curvoice.clone = null		// don't stop the multi-voice sequence
	get_voice(curvoice.id + ' ' + a.join(' '))
	if (p_voice)
		curvoice.clone = p_voice

	if (def)
		curvoice.default = 1 //true
}

// get / create a new voice
function new_voice(id) {
    var	v, p_v_sav,
	p_voice = voice_tb[0],
	n = voice_tb.length

	// if first explicit voice and no music, replace the default V:1
	if (n == 1
	 && p_voice.default) {
		delete p_voice.default
		if (!p_voice.time) {		// if no symbol yet
			p_voice.id = id
			p_voice.init = 0	// set back the global voice parameters
			return p_voice		// default voice
		}
	}
	for (v = 0; v < n; v++) {
		p_voice = voice_tb[v]
		if (p_voice.id == id)
			return p_voice		// old voice
	}

	p_voice = {
		v: v,
		id: id,
		time: staves_found >= 0 ? staves_found : 0,
		new: true,
		pos: {
//			dyn: 0,
//			gch: 0,
//			gst: 0,
//			orn: 0,
//			stm: 0,
//			tup: 0,
//			voc: 0,
//			vol: 0
		},
		scale: 1,
//		st: 0,
//		cst: 0,
		ulen: glovar.ulen,
		dur_fact: 1,
//		key: clone(parse.ckey),		// key at start of tune (parse / gene)
//		ckey: clone(parse.ckey),	// current key (parse / gene)
		meter: clone(glovar.meter),
		wmeasure: glovar.meter.wmeasure,
		staffnonote: 1,
		clef: {
			type: C.CLEF,
			clef_auto: true,
			clef_type: "a",		// auto
			time: 0
		},
		acc: [],		// accidentals of the measure (parse)
		sls: [],		// slurs - used in parsing and in generation
		hy_st: 0
	}

	voice_tb.push(p_voice);

	if (parse.state == 3) {
//		p_voice.key = parse.ckey	// (done later in music.js)
		p_voice.ckey = clone(parse.ckey)
		if (p_voice.ckey.k_bagpipe
		 && !p_voice.pos.stm) {
			p_voice.pos = clone(p_voice.pos)
			p_voice.pos.stm &= ~0x07
			p_voice.pos.stm |= C.SL_BELOW
		}
	}
	
//	par_sy.voices[v] = {
//		range: -1
//	}

	return p_voice
}

// this function is called at program start and on end of tune
function init_tune() {
	nstaff = -1;
	voice_tb = [];
	curvoice = null;
	new_syst(true);
	staves_found = -1;
	gene = {}
	a_de = []			// remove old decorations
	cross = {}			// new cross voice decorations
}

// treat V: with many voices
function do_cloning() {
    var	i,
	clone = curvoice.clone,
	vs = clone.vs,
	a = clone.a,
	bol = clone.bol,
	eol = parse.bol,
	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++;
	for (i = 0; i < vs.length; i++) {
		parse = Object.create(parse_sav) // create a new parse context
		parse.line = Object.create(parse_sav.line)
		get_voice(vs[i] + ' ' + a.join(' '))
		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,
	a = info_split(parm),
	vid = a.shift()

	if (!vid)
		return				// empty V:

	// 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(vs[0])

	// if many voices, memorize the start of sequence
	if (vs.length > 1) {
		vs.shift()
		curvoice.clone = {
			vs: vs,
			a: a.slice(0),		// copy the parameters
			bol: parse.iend
		}
		if (parse.file[curvoice.clone.bol - 1] != ']')
			curvoice.clone.bol++	// start of new line
	}

	set_kv_parm(a)

	key_trans()

	v = curvoice.v
	if (curvoice.new) {			// if new voice
		delete curvoice.new
		if (staves_found < 0) {		// if no %%score/%%staves
			curvoice.st = curvoice.cst = ++nstaff;
			par_sy.nstaff = nstaff;
			par_sy.voices[v] = {
				st: nstaff,
				range: v
			}
			par_sy.staves[nstaff] = {
				stafflines: curvoice.stafflines || "|||||",
				staffscale: 1
			}
		} else if (!par_sy.voices[v]) {
			curvoice.ignore = 1	// voice not declared in %%staves
			return
		}
	}

	if (!curvoice.filtered
	 && par_sy.voices[v]
	 && (parse.voice_opts
	  || parse.tune_v_opts)) {
		curvoice.filtered = true;
		voice_filter()
	}
}

// change state from 'tune header' to 'in tune body'
// curvoice is defined when called from get_voice()
function goto_tune() {
    var	v, p_voice

	set_page();
	write_heading();
	blk_flush()				// tune heading in a specific SVG

	if (glovar.new_nbar) {
		gene.nbar = glovar.new_nbar	// measure numbering
		glovar.new_nbar = 0
	} else {
		gene.nbar = 1
	}

	parse.state = 3				// in tune body

	// update some voice parameters
	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v];
		p_voice.ulen = glovar.ulen
		if (parse.ckey.k_bagpipe
		 && !p_voice.pos.stm) {
			p_voice.pos = clone(p_voice.pos)
			p_voice.pos.stm &= ~0x07
			p_voice.pos.stm |= C.SL_BELOW
		}
	}

	// initialize the voices when no %%staves/score	
	if (staves_found < 0) {
		v = voice_tb.length
		par_sy.nstaff =
			nstaff = v - 1
		while (--v >= 0) {
			p_voice = voice_tb[v];
			delete p_voice.new;		// old voice
			p_voice.st = p_voice.cst = v;
			par_sy.voices[v] = {
				st: v,
				range: v
			}
			par_sy.staves[v] = {
				stafflines: p_voice.stafflines || "|||||",
				staffscale: 1
			}
		}
	}
}
// abc2svg - lyrics.js - lyrics
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// parse a symbol line (s:)
function get_sym(p, cont) {
	var s, c, i, j, d

	if (curvoice.ignore)
		return

	// get the starting symbol of the lyrics
	if (cont) {					// +:
		s = curvoice.sym_cont
		if (!s) {
			syntax(1, "+: symbol line without music")
			return
		}
	} else {
		if (curvoice.sym_restart) {		// new music
			curvoice.sym_start = curvoice.sym_restart;
			curvoice.sym_restart = null
		}
		s = curvoice.sym_start
		if (!s)
			s = curvoice.sym
		if (!s) {
			syntax(1, "s: without music")
			return
		}
	}

	/* scan the symbol line */
	i = 0
	while (1) {
		while (p[i] == ' ' || p[i] == '\t')
			i++;
		c = p[i]
		if (!c)
			break
		switch (c) {
		case '|':
			while (s && s.type != C.BAR)
				s = s.next
			if (!s) {
				syntax(1, "Not enough measure bars for symbol line")
				return
			}
			s = s.next;
			i++
			continue
		case '!':
		case '"':
			j = ++i
			i = p.indexOf(c, j)
			if (i < 0) {
				syntax(1, c == '!' ?
					"No end of decoration" :
					"No end of chord symbol/annotation");
				i = p.length
				continue
			}
			d = p.slice(j - 1, i + 1)
			break
		case '*':
			break
		default:
			d = c.charCodeAt(0)
			if (d < 128) {
				d = char_tb[d]
				if (d.length > 1
				 && (d[0] == '!' || d[0] == '"')) {
					c = d[0]
					break
				}
			}
			syntax(1, errs.bad_char, c)
			break
		}

		/* store the element in the next note */
		while (s && s.type != C.NOTE)
			s = s.next
		if (!s) {
			syntax(1, "Too many elements in symbol line")
			return
		}
		switch (c) {
		default:
//		case '*':
			break
		case '!':
			a_dcn.push(d.slice(1, -1))
			deco_cnv(s, s.prev)
			break
		case '"':
			parse.line.index = j + 2	// (+ 's:')
			parse_gchord(d)
			if (a_gch)			// if no error
				csan_add(s)
			break
		}
		s = s.next;
		i++
	}
	curvoice.sym_cont = s
}

/* -- parse a lyric (vocal) line (w:) -- */
function get_lyrics(p, cont) {
    var s, word, i, j, ly, dfnt, ln, c, cf

	if (curvoice.ignore)
		return
	if ((curvoice.pos.voc & 0x07) != C.SL_HIDDEN)
		curvoice.have_ly = true

	// get the starting symbol of the lyrics
	if (cont) {					// +:
		s = curvoice.lyric_cont
		if (!s) {
			syntax(1, "+: lyric without music")
			return
		}
		if (p[0] == '~') {			// +:~next~words
			while (!s.a_ly)
				s = s.prev
			ly = s.a_ly[curvoice.lyric_line]
			p = ly.t.replace(/ /g,'~') + p
		}
		dfnt = get_font("vocal")
		if (gene.deffont != dfnt) {	// if vocalfont change
			if (gene.curfont == gene.deffont)
				gene.curfont = dfnt
			gene.deffont = dfnt
		}
	} else {
		set_font("vocal")
		if (curvoice.lyric_restart) {		// new music
			curvoice.lyric_start = s = curvoice.lyric_restart;
			curvoice.lyric_restart = null;
			curvoice.lyric_line = 0
		} else {
			curvoice.lyric_line++;
			s = curvoice.lyric_start
		}
		if (!s)
			s = curvoice.sym
		if (!s) {
			syntax(1, "w: without music")
			return
		}
	}

	/* scan the lyric line */
	i = 0
	cf = gene.curfont
	while (1) {
		while (p[i] == ' ' || p[i] == '\t')
			i++
		if (!p[i])
			break
		ln = 0
		j = parse.istart + i + 2	// start index
		switch (p[i]) { 
		case '|':
			while (s && s.type != C.BAR)
				s = s.next
			if (!s) {
				syntax(1, "Not enough measure bars for lyric line")
				return
			}
			s = s.next;
			i++
			continue
		case '-':
		case '_':
			word = p[i]
			ln = p[i] == '-' ? 2 : 3	// line continuation
			break
		case '*':
			word = ""
			break
		default:
			word = "";
			while (1) {
				if (!p[i])
					break
				switch (p[i]) {
				case '_':
				case '*':
				case '|':
					i--
				case ' ':
				case '\t':
					break
				case '~':
					word += ' '
					i++
					continue
				case '-':
					ln = 1		// start of line
					break
				case '\\':
					if (!p[++i])
						continue
					word += p[i++]
					continue
				case '$':
					word += p[i++]
					c = p[i]
					if (c == '0')
						gene.curfont = gene.deffont
					else if (c >= '1' && c <= '9')
						gene.curfont = get_font("u" + c)
					// fall thru
				default:
					word += p[i++]
					continue
				}
				break
			}
			break
		}

		/* store the word in the next note */
		while (s && s.type != C.NOTE)
			s = s.next
		if (!s) {
			syntax(1, "Too many words in lyric line")
			return
		}
		if (word
		 && (s.pos.voc & 0x07) != C.SL_HIDDEN) {
			ly = {
				t: word,
				font: cf,
				istart: j,
				iend: j + word.length
			}
			if (ln)
				ly.ln = ln
			if (!s.a_ly)
				s.a_ly = []
			s.a_ly[curvoice.lyric_line] = ly
			cf = gene.curfont
		}
		s = s.next;
		i++
	}
	curvoice.lyric_cont = s
}

// install the words under a note
// (this function is called during the generation)
function ly_set(s) {
    var	i, j, ly, d, s1, s2, p, w, spw, xx, sz, shift, dw, r,
	s3 = s,				// start of the current time sequence
	wx = 0,
	wl = 0,
	n = 0,
	dx = 0,
	a_ly = s.a_ly,
	align = 0

	// get the available horizontal space before the next lyric words
	for (s2 = s.ts_next; s2; s2 = s2.ts_next) {
		if (s2.seqst) {
			dx += s2.shrink
			n++			// number of symbols without word
		}
		if (s2.bar_type) {		// stop on a bar
			dx += 3			// and take some of its spacing
			break
		}
		if (!s2.a_ly)
			continue
		i = s2.a_ly.length
		while (--i >= 0) {
			ly = s2.a_ly[i]
			if (!ly)
				continue
			if (!ly.ln || ly.ln < 2)
				break
		}
		if (i >= 0)
			break
	}

	// define the offset of the words
	for (i = 0; i < a_ly.length; i++) {
		ly = a_ly[i]
		if (!ly)
			continue
		gene.curfont = ly.font
		ly.t = str2svg(ly.t)
		p = ly.t.replace(/<[^>]*>/g, '')	// remove the XML tags
		if (ly.ln >= 2) {
			ly.shift = 0
			continue
		}
		spw = cwid(' ') * ly.font.swfac
		w = ly.t.wh[0]
		r = abc2svg.lypre.exec(p)
		if (s.type == C.GRACE) {		// %%graceword
			shift = s.wl
		} else if (r) {
			r = r[0]
			if (p[0] == '(') {
				sz = spw
			} else {
				set_font(ly.font)
				if (p[r.length] == ' '
				 || r.slice(-1) == ':')
					sz = strwh(p.slice(0, r.length))[0]
				else
					sz = w * .2
			}
			shift = (w - sz) * .4
			if (shift > 14)
				shift = 14
			shift += sz
			if (p[0] >= '0' && p[0] <= '9') {
				if (shift > align)
					align = shift
			}
		} else {
			shift = w * .4
			if (shift > 14)
				shift = 14
		}
		ly.shift = shift
		if (shift > wl)
			wl = shift		// max left space
		w += spw * 1.5			// space after the syllable
		w -= shift			// right width
		if (w > wx)
			wx = w			// max width
	}

	// set the left space
	while (!s3.seqst)
		s3 = s3.ts_prev
	if (s3.ts_prev && s3.ts_prev.bar_type)
		wl -= 4			// don't move too much the measure bar
	if (s3.wl < wl) {
		s3.shrink += wl - s3.wl
		s3.wl = wl
	}

	// if not room enough, shift the following notes to the right
	dx -= 6
	if (dx < wx && s2) {
		dx = (wx - dx) / n
		s1 = s.ts_next
		while (1) {
			if (s1.seqst) {
				s1.shrink += dx
				s3.wr += dx	// (needed for end of line)
				s3 = s1
			}
			if (s1 == s2)
				break
			s1 = s1.ts_next
		}
	}

	if (align > 0) {
		for (i = 0; i < a_ly.length; i++) {
			ly = a_ly[i]
			if (ly && ly.t[0] >= '0' && ly.t[0] <= '9')
				ly.shift = align
		}
	}
} // ly_set()

/* -- draw the lyrics under (or above) notes -- */
/* (the staves are not yet defined) */
function draw_lyric_line(p_voice, j, y) {
	var	p, lastx, w, s, s2, ly, lyl, ln,
		hyflag, lflag, x0, shift

	if (p_voice.hy_st & (1 << j)) {
		hyflag = true;
		p_voice.hy_st &= ~(1 << j)
	}
	for (s = p_voice.sym; /*s*/; s = s.next)
		if (s.type != C.CLEF
		 && s.type != C.KEY && s.type != C.METER)
			break
	lastx = s.prev ? s.prev.x : tsfirst.x;
	x0 = 0
	for ( ; s; s = s.next) {
		if (s.a_ly)
			ly = s.a_ly[j]
		else
			ly = null
		if (!ly) {
			switch (s.type) {
			case C.REST:
			case C.MREST:
				if (lflag) {
					out_wln(lastx + 3, y, x0 - lastx);
					lflag = false;
					lastx = s.x + s.wr
				}
			}
			continue
		}
		if (ly.font != gene.curfont)		/* font change */
			gene.curfont = ly.font
		p = ly.t;
		ln = ly.ln || 0
		w = p.wh[0]
		shift = ly.shift
		if (hyflag) {
			if (ln == 3) {			// '_'
				ln = 2
			} else if (ln < 2) {		// not '-'
				out_hyph(lastx, y, s.x - shift - lastx);
				hyflag = false;
				lastx = s.x + s.wr
			}
		}
		if (lflag
		 && ln != 3) {				// not '_'
			out_wln(lastx + 3, y, x0 - lastx + 3);
			lflag = false;
			lastx = s.x + s.wr
		}
		if (ln >= 2) {				// '-' or '_'
			if (x0 == 0 && lastx > s.x - 18)
				lastx = s.x - 18
			if (ln == 2)			// '-'
				hyflag = true
			else
				lflag = true;
			x0 = s.x - shift
			continue
		}
		x0 = s.x - shift;
		if (ln)					// '-' at end
			hyflag = true
		if (user.anno_start || user.anno_stop) {
			s2 = {
				p_v: s.p_v,
				st: s.st,
				istart: ly.istart,
				iend: ly.iend,
				ts_prev: s,
				ts_next: s.ts_next,
				x: x0,
				y: y,
				ymn: y,
				ymx: y + gene.curfont.size,
				wl: 0,
				wr: w
			}
			anno_start(s2, 'lyrics')
		}
		xy_str(x0, y, p)
		anno_stop(s2, 'lyrics')
		lastx = x0 + w
	}
	if (hyflag) {
		hyflag = false;
		x0 = realwidth - 10
		if (x0 < lastx + 10)
			x0 = lastx + 10;
		out_hyph(lastx, y, x0 - lastx)
		if (p_voice.s_next && p_voice.s_next.fmt.hyphencont)
			p_voice.hy_st |= (1 << j)
	}

	/* see if any underscore in the next line */
	for (p_voice.s_next; s; s = s.next) {
		if (s.type == C.NOTE) {
			if (!s.a_ly)
				break
			ly = s.a_ly[j]
			if (ly && ly.ln == 3) {		 // '_'
				lflag = true;
				x0 = realwidth - 15
				if (x0 < lastx + 12)
					x0 = lastx + 12
			}
			break
		}
	}
	if (lflag) {
		out_wln(lastx + 3, y, x0 - lastx + 3);
		lflag = false
	}
}

function draw_lyrics(p_voice, nly, a_h, y,
				incr) {	/* 1: below, -1: above */
	var	j, top,
		sc = staff_tb[p_voice.st].staffscale;

	set_font("vocal")
	if (incr > 0) {				/* under the staff */
		if (y > -tsfirst.fmt.vocalspace)
			y = -tsfirst.fmt.vocalspace;
		y *= sc
		for (j = 0; j < nly; j++) {
			y -= a_h[j] * 1.1;
			draw_lyric_line(p_voice, j,
				y + a_h[j] * .22)	// (descent)
		}
		return y / sc
	}

	/* above the staff */
	top = staff_tb[p_voice.st].topbar + tsfirst.fmt.vocalspace
	if (y < top)
		y = top;
	y *= sc
	for (j = nly; --j >= 0;) {
		draw_lyric_line(p_voice, j, y + a_h[j] * .22)
		y += a_h[j] * 1.1
	}
	return y / sc
}

// -- draw all the lyrics --
/* (the staves are not yet defined) */
function draw_all_lyrics() {
	var	p_voice, s, v, nly, i, x, y, w, a_ly, ly,
		lyst_tb = new Array(nstaff + 1),
		nv = voice_tb.length,
		h_tb = new Array(nv),
		nly_tb = new Array(nv),
		above_tb = new Array(nv),
		rv_tb = new Array(nv),
		top = 0,
		bot = 0,
		st = -1

	/* compute the number of lyrics per voice - staff
	 * and their y offset on the staff */
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		if (!p_voice.sym)
			continue
		if (p_voice.st != st) {
			top = 0;
			bot = 0;
			st = p_voice.st
		}
		nly = 0
		if (p_voice.have_ly) {
			if (!h_tb[v])
				h_tb[v] = []
			for (s = p_voice.sym; s; s = s.next) {
				a_ly = s.a_ly
				if (!a_ly)
					continue
/*fixme:should get the real width*/
				x = s.x;
				w = 10
				for (i = 0; i < a_ly.length; i++) {
					ly = a_ly[i]
					if (ly) {
						x -= ly.shift;
						w = ly.t.wh[0]
						break
					}
				}
				y = y_get(p_voice.st, 1, x, w)
				if (top < y)
					top = y;
				y = y_get(p_voice.st, 0, x, w)
				if (bot > y)
					bot = y
				while (nly < a_ly.length)
					h_tb[v][nly++] = 0
				for (i = 0; i < a_ly.length; i++) {
					ly = a_ly[i]
					if (!ly)
						continue
					if (!h_tb[v][i]
					 || ly.t.wh[1] > h_tb[v][i])
						h_tb[v][i] = ly.t.wh[1]
				}
			}
		} else {
			y = y_get(p_voice.st, 1, 0, realwidth)
			if (top < y)
				top = y;
			y = y_get(p_voice.st, 0, 0, realwidth)
			if (bot > y)
				bot = y
		}
		if (!lyst_tb[st])
			lyst_tb[st] = {}
		lyst_tb[st].top = top;
		lyst_tb[st].bot = bot;
		nly_tb[v] = nly
		if (nly == 0)
			continue
		if (p_voice.pos.voc)
			above_tb[v] = (p_voice.pos.voc & 0x07) == C.SL_ABOVE
		else if (voice_tb[v + 1]
/*fixme:%%staves:KO - find an other way..*/
		      && voice_tb[v + 1].st == st
		      && voice_tb[v + 1].have_ly)
			above_tb[v] = true
		else
			above_tb[v] = false
		if (above_tb[v])
			lyst_tb[st].a = true
		else
			lyst_tb[st].b = true
	}

	/* draw the lyrics under the staves */
	i = 0
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		if (!p_voice.sym)
			continue
		if (!p_voice.have_ly)
			continue
		if (above_tb[v]) {
			rv_tb[i++] = v
			continue
		}
		st = p_voice.st;
// don't scale the lyrics
		set_dscale(st, true)
		if (nly_tb[v] > 0)
			lyst_tb[st].bot = draw_lyrics(p_voice, nly_tb[v],
							h_tb[v],
							lyst_tb[st].bot, 1)
	}

	/* draw the lyrics above the staff */
	while (--i >= 0) {
		v = rv_tb[i];
		p_voice = voice_tb[v];
		st = p_voice.st;
		set_dscale(st, true);
		lyst_tb[st].top = draw_lyrics(p_voice, nly_tb[v],
						h_tb[v],
						lyst_tb[st].top, -1)
	}

	/* set the max y offsets of all symbols */
	for (v = 0; v < nv; v++) {
		p_voice = voice_tb[v]
		if (!p_voice.sym)
			continue
		st = p_voice.st;
		if (lyst_tb[st].a) {
			top = lyst_tb[st].top + 2
			for (s = p_voice.sym; s; s = s.next) {
/*fixme: may have lyrics crossing a next symbol*/
				if (s.a_ly) {
/*fixme:should set the real width*/
					y_set(st, 1, s.x - 2, 10, top)
				}
			}
		}
		if (lyst_tb[st].b) {
			bot = lyst_tb[st].bot - 2
			if (nly_tb[p_voice.v] > 0) {
				for (s = p_voice.sym; s; s = s.next) {
					if (s.a_ly) {
/*fixme:should set the real width*/
						y_set(st, 0, s.x - 2, 10, bot)
					}
				}
			} else {
				y_set(st, 0, 0, realwidth, bot)
			}
		}
	}
}
// abc2svg - gchord.js - chord symbols
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// -- 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,
	i, j, istart, iend,
		ann_font = get_font("annotation"),
		h_ann = ann_font.size,
		line = parse.line

	function get_float() {
		var txt = ''

		while (1) {
			c = text[i++]
			if ("1234567890.-".indexOf(c) < 0)
				return parseFloat(txt)
			txt += c
		}
	} // get_float()

	istart = parse.bol + line.index
	if (type.length > 1) {			// U:
		text = type.slice(1, -1);
		iend = istart + 1
	} else {
		i = ++line.index		// search the ending double quote
		while (1) {
			j = line.buffer.indexOf('"', i)
			if (j < 0) {
				syntax(1, "No end of chord symbol/annotation")
				return
			}
			if (line.buffer[j - 1] != '\\'
			 || line.buffer[j - 2] == '\\')	// (string ending with \\")
				break
			i = j + 1
		}
		text = cnv_escape(line.buffer.slice(line.index, j))
		line.index = j
		iend = parse.bol + line.index + 1
	}

	if (ann_font.pad)
		h_ann += ann_font.pad
	i = 0;
	type = 'g'
	while (1) {
		c = text[i]
		if (!c)
			break
		gch = {
			text: "",
			istart: istart,
			iend: iend,
			font: ann_font
		}
		switch (c) {
		case '@':
			type = c;
			i++;
			x_abs = get_float()
			if (c != ',') {
				syntax(1, "',' lacking in annotation '@x,y'");
				y_abs = 0
			} else {
				y_abs = get_float()
				if (c != ' ')
					i--
			}
			gch.x = x_abs;
			gch.y = y_abs
			break
		case '^':
			gch.pos = C.SL_ABOVE
			// fall thru
		case '_':
			if (c == '_')
				gch.pos = C.SL_BELOW
			// fall thru
		case '<':
		case '>':
			i++;
			type = c
			break
		default:
			switch (type) {
			case 'g':
				gch.font = get_font("gchord")
				gch.pos = curvoice.pos.gch || C.SL_ABOVE
				break
			case '^': 
				gch.pos = C.SL_ABOVE
				break
			case '_':
				gch.pos = C.SL_BELOW
				break
			case '@':
				gch.x = x_abs;
				y_abs -= h_ann;
				gch.y = y_abs
				break
			}
			break
		}
		gch.type = type
		while (1) {
			c = text[i]
			if (!c)
				break
			switch (c) {
			default:
				gch.text += c;
				i++
				continue
			case '&':			/* skip "&xxx;" */
				while (1) {
					gch.text += c;
					c = text[++i]
					switch (c) {
					default:
						continue
					case ';':
					case undefined:
					case '\\':
						break
					}
					break
				}
				if (c == ';') {
					i++;
					gch.text += c
					continue
				}
				break
			case '\n':		// abcm2ps compatibility
			case ';':
				break
			}
			i++
			break
		}
		gch.otext = gch.text	// save for play accompaniment
		if (!a_gch)
			a_gch = []
		a_gch.push(gch)
	}
}

// transpose a chord symbol
	function gch_tr1(p, tr) {
	    var	i, o, n, ip,
		csa = p.split('/')

		tr = abc2svg.b40l5[(tr + 202) % 40]	// transpose in the line of fifth
		for (i = 0; i < csa.length; i++) {	// main and optional bass
			p = csa[i];
			o = p.search(/[A-G]/)
			if (o < 0)
				continue		// strange chord symbol!
			ip = o + 1

//	bbb fb cb gb db ab eb bb  f c g d a e b f# c# g# d# a# e# b# f##
//	 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5  6  7  8  9 10 11 12  13
			n = "FCGDAEB".indexOf(p[o]) - 1
			if (p[ip] == '#' || p[ip] == '\u266f') {
				n += 7
				ip++
			} else if (p[ip] == 'b' || p[ip] == '\u266d') {
				n -= 7
				ip++
			}
			n += tr					// transpose

//			// remove chords with double sharps/flats
//			if ((!i && n > 7)			// main chord
//			 || (i && n > 12))			// bass
//				n -= 12
//			else if (i < -7)
//				n += 12

			csa[i] = p.slice(0, o)
				+ "FCGDAEB"[(n + 22) % 7]
				+ (n >= 13 ? '##'
					: n >= 6 ? '#'
					: n <= -9 ? 'bb'
					: n <= -2 ? 'b'
					: '')
				+ p.slice(ip)
		}
		return csa.join('/')
	} // gch_tr1

// parser: add the parsed list of chord symbols and annotations
//	to the symbol (note, rest or bar)
//	and transpose the chord symbols
function csan_add(s) {
    var	i, gch

	// there cannot be chord symbols on measure bars
	if (s.type == C.BAR) {
		for (i = 0; i < a_gch.length; i++) {
			if (a_gch[i].type == 'g') {
				error(1, s,
				       "There cannot be chord symbols on measure bars")
				a_gch.splice(i)
			}
		}
	}

	if (curvoice.tr_sco
	 || curvoice.tr_snd) {
		for (i = 0; i < a_gch.length; i++) {
			gch = a_gch[i]
			if (gch.type == 'g') {
				if (curvoice.tr_snd40)
					gch.otext = gch_tr1(gch.text, curvoice.tr_snd40)
				if (curvoice.tr_sco)
					gch.text = gch_tr1(gch.text, curvoice.tr_sco)
			}
		}
	}

	if (s.a_gch)
		s.a_gch = s.a_gch.concat(a_gch)
	else
		s.a_gch = a_gch
	a_gch = null
} // csan_add

// generator: build the chord symbols / annotations
// (possible hook)
Abc.prototype.gch_build = function(s) {

	/* split the chord symbols / annotations
	 * and initialize their vertical offsets */
	var	gch, wh, xspc, ix,
		y_left = 0,
		y_right = 0,
		GCHPRE = .4;		// portion of chord before note

	// 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') {
			gch.text = gch.text.replace(/##|#|=|bb|b/g,
				function(x) {
					switch (x) {
					case '##': return "&#x1d12a;"
					case '#': return "\u266f"
					case '=': return "\u266e"
					case 'b': return "\u266d"
					}
					return "&#x1d12b;"
				});
		} else {
			if (gch.type == '@'
			 && !user.anno_start && !user.anno_stop) {
				set_font(gch.font)
				gch.text = str2svg(gch.text)
				continue		/* no width */
			}
		}

		/* set the offsets and widths */
		set_font(gch.font);
		gch.text = str2svg(gch.text)
		wh = gch.text.wh
		switch (gch.type) {
		case '@':
			break
		default:
//		case 'g':			// chord symbol
//		case '^':			/* above */
//		case '_':			/* below */
			xspc = wh[0] * GCHPRE
			if (xspc > 8)
				xspc = 8;
			gch.x = -xspc;
			break
		case '<':			/* left */
			gch.x = -(wh[0] + 6);
			y_left -= wh[1];
			gch.y = y_left + wh[1] / 2
			break
		case '>':			/* right */
			gch.x = 6;
			y_right -= wh[1];
			gch.y = y_right + wh[1] / 2
			break
		}
	}

	/* move upwards the top and middle texts */
	y_left /= 2;
	y_right /= 2
	for (ix = 0; ix < s.a_gch.length; ix++) {
		gch = s.a_gch[ix]
		switch (gch.type) {
		case '<':			/* left */
			gch.y -= y_left
			break
		case '>':			/* right */
			gch.y -= y_right
			break
		}
	}
}

// -- draw the chord symbols and annotations
// (the staves are not yet defined)
// (unscaled delayed output)
// (possible hook)
Abc.prototype.draw_gchord = function(i, s, x, y) {
	if (s.invis && s.play)	// play sequence: no chord nor annotation
		return
    var	y2,
	an = s.a_gch[i],
	h = an.text.wh[1],
	pad = an.font.pad,
	w = an.text.wh[0] + pad * 2,
	dy = h * .22			// descent

	if (an.font.figb) {
		h *= 2.4
		dy += an.font.size * 1.3
	}

	switch (an.type) {
	case '_':			// below
		y -= h + pad
		break
	case '^':			// above
		y += pad
		break
	case '<':			// left
	case '>':			// right
		if (an.type == '<') {
/*fixme: what symbol space?*/
			if (s.notes[0].acc)
				x -= s.notes[0].shac
			x -= pad
		} else {
			if (s.xmx)
				x += s.xmx
			if (s.dots)
				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
		else
			y -= h + 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 + pad + 2
			if (y2 > staff_tb[s.st].ann_top)
				staff_tb[s.st].ann_top = y2
		} else {
			y2 = y - 2
			if (y2 < staff_tb[s.st].ann_bot)
				staff_tb[s.st].ann_bot = y2
		}
		break
	}

	if (an.type != '@') {
		if (y >= 0)
			y_set(s.st, 1, x, w, y + h + pad + 2)
		else
			y_set(s.st, 0, x, w, y - pad)
	}

	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 + dy, an.text)
	if (user.anno_stop)
		user.anno_stop("annot", an.istart, an.iend,
			x - 2, y + h + 2, w + 4, h + 4, s)
} // draw_gchord()

// draw all chord symbols
function draw_all_chsy() {
    var	s, san1, an, i, x, y, w,
	n_an = 0,		// max number of annotations
	minmax = new Array(nstaff + 1)

	// set a vertical offset to all the chord symbols/annotations
	function set_an_yu(j) {
	    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.pos == C.SL_ABOVE) {
				x = s.x + an.x
				w = an.text.wh[0]
				if (w && x + w > realwidth)
					x = realwidth - w // let the text in the page
				y = y_get(s.st, 1, x, w)	// y / staff
				if (an.type == 'g' && y < minmax[s.st].yup)
					y = minmax[s.st].yup
			} else if (an.pos == C.SL_BELOW
				|| an.pos == C.SL_HIDDEN) {
				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.pos != C.SL_BELOW)
				continue
			x = s.x + an.x
			w = an.text.wh[0]
			if (w && x + w > realwidth)	// let the text inside the page
				x = realwidth - w
			y = y_get(s.st, 0, x, w) - 2	// 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]
				x = s.x + an.x
				w = an.text.wh[0]
				if (w && x + w > realwidth)
					x = realwidth - w
				if (an.pos == C.SL_ABOVE) {
					y = y_get(s.st, true, x, w)
					if (y > minmax[s.st].yup)
						minmax[s.st].yup = y
				} else if (an.pos == C.SL_BELOW) {
					y = y_get(s.st, false, x, w) - 2
					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()
// abc2svg - tail.js
//
// Copyright (C) 2014-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// 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.anno_a = anno_a
Abc.prototype.cfmt = function() { return cfmt };
Abc.prototype.clone = clone;
Abc.prototype.clr_sty = clr_sty
Abc.prototype.deco_put = function(nm, s) {
	a_dcn.push(nm)
	deco_cnv(s)
}
Abc.prototype.defs_add = defs_add
Abc.prototype.dh_put = function(nm, s, nt) {
	a_dcn.push(nm)
	dh_cnv(s, nt)
}
Abc.prototype.draw_meter = draw_meter
Abc.prototype.draw_note = draw_note;
Abc.prototype.errs = errs;
Abc.prototype.font_class = font_class;
Abc.prototype.gch_tr1 = gch_tr1;
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 };
Abc.prototype.get_font = get_font;
Abc.prototype.get_font_style = function() { return font_style };
Abc.prototype.get_glyphs = function() { return glyphs };
Abc.prototype.get_img = function() { return img };
Abc.prototype.get_lwidth = get_lwidth
Abc.prototype.get_maps = function() { return maps };
Abc.prototype.get_multi = function() { return multicol };
Abc.prototype.get_newpage = function() {
	if (block.newpage) {
		block.newpage = false;
		return true
	}
};
Abc.prototype.get_parse = function() { return parse }
Abc.prototype.get_posy = function() { return posy }
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_user = function() { return user }
Abc.prototype.get_voice_tb = function() { return voice_tb };
Abc.prototype.glout = glout
Abc.prototype.glovar = function() { return glovar }
Abc.prototype.info = function() { return info };
Abc.prototype.new_block = new_block;
Abc.prototype.out_arp = out_arp;
Abc.prototype.out_deco_str = out_deco_str;
Abc.prototype.out_deco_val = out_deco_val;
Abc.prototype.out_ltr = out_ltr;
Abc.prototype.param_set_font = param_set_font;
Abc.prototype.part_seq = part_seq
Abc.prototype.psdeco = empty_function;
Abc.prototype.psxygl = empty_function;
Abc.prototype.set_cur_sy = function(sy) { cur_sy = sy };
Abc.prototype.set_curvoice = function(p_v) { curvoice = p_v }
Abc.prototype.set_dscale = set_dscale;
Abc.prototype.set_font = set_font;
Abc.prototype.set_a_gch = function(s, a) { a_gch = a; csan_add(s) }
Abc.prototype.set_hl = set_hl
Abc.prototype.set_map = set_map
Abc.prototype.set_page = set_page
Abc.prototype.set_pagef = function() { blkdiv = 1 }
Abc.prototype.set_realwidth = function(v) { realwidth = v }
Abc.prototype.set_scale = set_scale;
Abc.prototype.set_sscale = set_sscale
Abc.prototype.set_tsfirst = function(s) { tsfirst = s };
Abc.prototype.set_v_param = set_v_param;
Abc.prototype.str2svg = str2svg
Abc.prototype.strwh = strwh;
Abc.prototype.stv_g = function() { return stv_g };
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;
Abc.prototype.y_get = y_get
Abc.prototype.y_set = y_set
}	// end of Abc()

// compatibility
var Abc = abc2svg.Abc

// nodejs
if (typeof module == 'object' && typeof exports == 'object') {
	exports.abc2svg = abc2svg;
	exports.Abc = Abc
}
// abc2svg - modules.js - module handling
//
// Copyright (C) 2018-2025 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
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg-core is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg-core.  If not, see <http://www.gnu.org/licenses/>.

// empty function
if (!abc2svg.loadjs) {
    abc2svg.loadjs = function(fn, onsuccess, onerror) {
	if (onerror)
		onerror(fn)
    }
}

abc2svg.modules = {
	ambitus: {},
	begingrid: { fn: 'grid3' },
	beginps: { fn: 'psvg' },
	break: {},
	capo: {},
	chordnames: {},
	clip: {},
	clairnote: { fn: 'clair' },
	voicecombine: { fn: 'combine' },
	diagram: { fn: 'diag' },
	equalbars: {},
	fit2box: {},
	gamelan: {},
	grid: {},
	grid2: {},
	jazzchord: {},
	jianpu: {},
	mdnn: {},
	MIDI: {},
	nns: {},
	pageheight: { fn: 'page' },
	pedline: {},
	percmap: { fn: 'perc' },
	playswing: { fn: 'swing' },
	roman: {},
	soloffs: {},
	sth: {},
	strtab: {},
	temperament: { fn: 'temper' },
	temponame: { fn: 'tempo' },
	tropt: {},
	titleformat: { fn: 'tunhd' },

	nreq: 0,

	// scan the file and find the required modules
	// @file: ABC file
	// @relay: (optional) callback function for continuing the treatment
	// @errmsg: (optional) function to display an error message if any
	//	This function gets one argument: the message
	// return true when all modules are loaded
	load: function(file, relay, errmsg) {

		function get_errmsg() {
			if (typeof user == 'object' && user.errmsg)
				return user.errmsg
			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()
		}

		// test if some keyword in the file
	    var	m, i, fn,
		nreq_i = this.nreq,
		ls = file.match(/(%%|I:).+?\b/g)

		if (!ls)
			return true
		this.cbf = relay ||		// (only one callback function)
			function(){}
		this.errmsg = errmsg || get_errmsg()

		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
			if (m.fn)
				fn = m.fn
			this.nreq++
			abc2svg.loadjs(fn + "-1.js",
					load_end,
					function () {
						abc2svg.modules.errmsg(
							'Error loading the module ' + fn)
						load_end()
					})
		}
		return this.nreq == nreq_i
	}
} // modules
abc2svg.version="v1.22.34";abc2svg.vdate="2025-11-22"