// clip.js - module to handle the %%clip command
//
// Copyright (C) 2018-2024 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 "%%clip" appears in a ABC source.
//
// Parameters
// %%clip start_measure_nb [":" num "/" den] "-" end_measure_nb [":" num "/" den]
if (typeof abc2svg == "undefined")
var abc2svg = {}
abc2svg.clip = {
get_clip: function(parm) {
var C = abc2svg.C
// get the start/stop points
function get_symsel(a) {
var j, d, sq,
b = a.match(/(\d+)([a-z]?)(:\d+\/\d+)?/)
if (!b)
return
if (b[2])
sq = b[2].charCodeAt(0) - 0x61
if (!b[3])
return {m: b[1], t: 0, sq: sq} // on measure bar
a = b[3].match(/:(\d+)\/(\d+)/)
if (!a || a[2] < 1)
return
return {m: b[1], t: a[1] * C.BLEN / a[2], sq: sq}
} // get_symsel()
var b, c,
a = parm.split(/[ -]/)
if (a.length != 3) {
this.syntax(1, this.errs.bad_val, "%%clip")
return
}
if (!a[1])
b = {m: 0, t: 0}
else
b = get_symsel(a[1]);
c = get_symsel(a[2])
if (!b || !c) {
this.syntax(1, this.errs.bad_val, "%%clip")
return
}
this.cfmt().clip = [b, c]
}, // get_clip()
// cut the tune
do_clip: function() {
var C = abc2svg.C,
abc = this,
voice_tb = this.get_voice_tb(),
cfmt = this.cfmt()
// check the pointers (ties and slurs)
function chkptr(last) {
var i, sl, s2, m, nt,
tim = last.time,
s = abc.get_tsfirst()
do {
if (s.sls) {
for (i = 0; i < s.sls.length; i++) {
sl = s.sls[i]
s2 = sl.se // slur end
if (s2.time >= tim)
sl.loc = 'o' // no end
}
}
if (s.ti1 // if tie
&& s.time + s.dur >= tim) { // ending out of the clip seq.
for (m = 0; m <= s.nhd; m++) {
nt = s.notes[m]
if (nt.tie_e && nt.tie_e.s.time >= tim)
delete nt.tie_e // no end
}
}
s = s.ts_next
} while (s)
} // chkptr()
// go to a global (measure + time)
function go_global_time(s, sel) {
var s2, bar_time, seq
if (sel.m <= 1) { // special case: there is no measure 0/1
if (sel.m == 1) {
for (s2 = s; s2; s2 = s2.ts_next) {
if (s2.type == C.BAR
&& s2.time != 0)
break
}
if (s2.time < voice_tb[abc.get_cur_sy().top_voice].
meter.wmeasure)
s = s2
}
} else {
for ( ; s; s = s.ts_next) {
if (s.type == C.BAR
&& s.bar_num >= sel.m)
break
}
if (!s)
return // null
if (sel.sq) {
seq = sel.sq
for (s = s.ts_next; s; s = s.ts_next) {
if (s.type == C.BAR
&& s.bar_num == sel.m) {
if (--seq == 0)
break
}
}
if (!s)
return // null
}
}
if (sel.t == 0)
return s;
bar_time = s.time + sel.t
while (s.time < bar_time) {
s = s.ts_next
if (!s)
return s
}
do {
s = s.ts_prev // go back to the previous sequence
} while (!s.seqst)
return s
}
var s, s2, sy, p_voice, v
// remove the beginning of the tune
s = this.get_tsfirst()
if (cfmt.clip[0].m > 0
|| cfmt.clip[0].t > 0) {
s = go_global_time(s, cfmt.clip[0])
if (!s) {
this.set_tsfirst(null)
return
}
// update the start of voices
sy = this.get_cur_sy()
for (s2 = this.get_tsfirst(); s2 != s; s2 = s2.ts_next) {
switch (s2.type) {
case C.CLEF:
s2.p_v.clef = s2
break
case C.KEY:
s2.p_v.key = this.clone(s2.as.u.key)
s2.p_v.ckey = this.clone(s2.as.u.ckey)
break
case C.METER:
s2.p_v.meter = this.clone(s2.as.u.meter)
break
case C.STAVES:
sy = s2.sy;
this.set_cur_sy(sy)
break
}
}
for (v = 0; v < voice_tb.length; v++) {
p_voice = voice_tb[v]
for (s2 = s; s2; s2 = s2.ts_next) {
if (s2.v == v) {
delete s2.prev
break
}
}
p_voice.sym = s2
}
s2 = this.get_tsfirst()
if (s != s2) {
if (s2.type == C.STAVES) {
s2.ts_next = s
s.ts_prev = s2
s2.next = s2.p_v.sym
s2.p_v.sym = s2
s2.next.prev = s2
} else {
this.set_tsfirst(s)
delete s.ts_prev
}
}
}
/* remove the end of the tune */
s = go_global_time(s, cfmt.clip[1])
if (!s)
return
/* keep the current sequence */
do {
s = s.ts_next
if (!s)
return
} while (!s.seqst)
/* cut the voices */
for (v = 0; v < voice_tb.length; v++) {
p_voice = voice_tb[v]
for (s2 = s.ts_prev; s2; s2 = s2.ts_prev) {
if (s2.v == v) {
delete s2.next
break
}
}
if (!s2)
p_voice.sym = null
}
delete s.ts_prev.ts_next
chkptr(s.ts_prev) // check the pointers (ties and slurs)
}, // do_clip()
do_pscom: function (of, text) {
if (text.slice(0, 5) == "clip ")
abc2svg.clip.get_clip.call(this, text)
else
of(text)
},
set_bar_num: function(of) {
of()
if (this.cfmt().clip)
abc2svg.clip.do_clip.call(this)
},
set_hooks: function(abc) {
abc.do_pscom = abc2svg.clip.do_pscom.bind(abc, abc.do_pscom);
abc.set_bar_num = abc2svg.clip.set_bar_num.bind(abc, abc.set_bar_num)
}
} // clip
if (!abc2svg.mhooks)
abc2svg.mhooks = {}
abc2svg.mhooks.clip = abc2svg.clip.set_hooks