// sth.js - module to set the stem heights
//
// Copyright (C) 2018-2023 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg.  If not, see <http://www.gnu.org/licenses/>.
//
// This module is loaded when "%%sth" appears in a ABC source.
//
// Parameters
//	%%sth h1 h2 h3 ...
// The values h1, h2, .. are applied to the following notes which
// have a stem and which are not inside a beam.
// The value may be '*' for keeping the original stem length.

if (typeof abc2svg == "undefined")
    var	abc2svg = {}

abc2svg.sth = {

// function called after beam calculation
    recal_beam: function(bm, s) {
    var staff_tb = this.get_staff_tb(),
	y = staff_tb[s.st].y,
	s2 = bm.s2,
	y2 = staff_tb[s2.st].y

	if (s.sth != undefined)
		s.ys = s.sth
	if (s2.sth != undefined)
		s2.ys = s2.sth;
	bm.a = (s.ys + y - s2.ys - y2) / (s.xs - s2.xs);
	bm.b = s.ys - s.xs * bm.a + y
	while (1) {
		s.ys = bm.a * s.xs + bm.b - y
		if (s.stem > 0)
			s.ymx = s.ys + 2.5
		else
			s.ymn = s.ys - 2.5;
		s = s.next
		if (s == s2)
			break
	}
    },

// function called after the stem heights have been computed
    set_sth: function() {
    var s, h, v, sth_a, p_voice,
	voice_tb = this.get_voice_tb()

	for (v = 0; v < voice_tb.length; v++) {
		p_voice = voice_tb[v]
		if (p_voice.sth != null)	// if no stem length in this voice
			continue
		sth_a = []
		for (s = p_voice.sym; s; s = s.next) {
			if (s.sth) {
				sth_a = s.sth;
				s.sth = null
			}
			if (sth_a.length == 0
			 || s.nflags <= -2 || s.stemless
			 || !(s.beam_st || s.beam_end))
				continue
			h = sth_a.shift()
			if (h == '*')
				continue	// no change
			if (h == '|') {		// skip to the next measure bar
				for (s = s.next; s; s = s.next) {
					if (s.bar_type)
						break
				}
				continue
			}
			h = Number(h)
			if (isNaN(h) || !h)
				continue	// fixme: error
			if (s.stem >= 0) {
				s.ys = s.y + h;
				s.ymx = (s.ys + 2.5) | 0
			} else {
				s.ys = s.y - h;
				s.ymn = (s.ys - 2.5) | 0
			}
			s.sth = s.ys
		}
	}
    }, // set_sth()

    calculate_beam: function(of, bm, s1) {
    var	done = of(bm, s1)
	if (done && bm.s2 && (s1.sth || bm.s2.sth))
		abc2svg.sth.recal_beam.call(this, bm, s1)
	return done
    },

    new_note: function(of, grace, tp_fact) {
    var	C = abc2svg.C,
	s = of(grace, tp_fact),
	curvoice = this.get_curvoice()

	if (curvoice.sth && s && s.type == C.NOTE) {
		s.sth = curvoice.sth;
		curvoice.sth = null	// some stem widths in this voice
	}
	return s
    },

    set_fmt: function(of, cmd, param) {
	if (cmd == "sth") {
	    var	curvoice = this.get_curvoice()
		if (curvoice)
			curvoice.sth = param.split(/[ \t;-]+/)
		return
	}
	of(cmd, param)
    },

    set_stems: function(of) {
	of();
	abc2svg.sth.set_sth.call(this)
    },

    set_hooks: function(abc) {
	abc.calculate_beam = abc2svg.sth.calculate_beam.bind(abc, abc.calculate_beam);
	abc.new_note = abc2svg.sth.new_note.bind(abc, abc.new_note);
	abc.set_format = abc2svg.sth.set_fmt.bind(abc, abc.set_format);
	abc.set_stems = abc2svg.sth.set_stems.bind(abc, abc.set_stems)
    }
} // sth

if (!abc2svg.mhooks)
	abc2svg.mhooks = {}
abc2svg.mhooks.sth = abc2svg.sth.set_hooks