// clair.js - module to output Clairnote sheets (https:clairnote.org)
//
// Copyright (C) 2019-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 "%%clairnote" appears in a ABC source.
//
// Parameters (none)
// %%clairnote 1
if (typeof abc2svg == "undefined")
var abc2svg = {}
abc2svg.clair = {
// table for helper lines (from C, to c'')
// index = (note pitch + 16)
// value = array of line numbers / null
hl_tb: [
new Int8Array([-10,-8,-6,-4,-2]), // _B,, -18
new Int8Array([-10,-9,-8,-6,-4,-2]), // B,, -17
new Int8Array([-8,-6,-4,-2]), // C, -16
new Int8Array([-8,-6,-4,-2]), // ^C, -15
new Int8Array([-8,-7,-6,-4,-2]), // D, -14
new Int8Array([-6,-4,-2]), // ^D, -13
new Int8Array([-6,-4,-2]), // E, -12
new Int8Array([-6,-4,-2]), // F, -11
new Int8Array([-6,-5,-4,-2]), // ^F, -10
new Int8Array([-4,-2]), // G, -9
new Int8Array([-4,-2]), // ^G, -8
new Int8Array([-4,-2]), // A, -7
new Int8Array([-4,-3,-2]), // _B, -6
new Int8Array([-2]), // B, -5
new Int8Array([-2]), // C -4
new Int8Array([-2]), // ^C -3
new Int8Array([-2,-1]), // D -2
null, // ^D -1
null, // E 0
null, // F 1
new Int8Array([1]), // ^F 2
null, // G 3
null, // ^G 4
null, // A 5
new Int8Array([3,4]), // _B 6
new Int8Array([4]), // B 7
new Int8Array([4]), // c 8
new Int8Array([4]), // ^c 9
new Int8Array([4,5]), // d 10
null, // _e 11
null, // e 12
null, // f 13
new Int8Array([7]), // ^f 14
null, // g 15
null, // ^g 16
null, // a 17
new Int8Array([9,10]), // _b 18
new Int8Array([10]), // b 19
new Int8Array([10]), // c' 20
new Int8Array([10]), // ^c' 21
new Int8Array([10,11]), // d' 22
new Int8Array([10,11]), // _e' 23
new Int8Array([10,12]), // e' 24
new Int8Array([10,12]), // f' 25
new Int8Array([10,12,13]), // ^f' 26
new Int8Array([10,12,13]), // g' 27
new Int8Array([10,12,14]), // ^g' 28
new Int8Array([10,12,14]), // a' 29
new Int8Array([10,12,14,15]), // _b' 30
new Int8Array([10,12,14,15]), // b' 31
new Int8Array([10,12,14,16]) // c'' 32
],
// draw the helper lines
draw_hl: function(of, s) {
if (!s.p_v.clair) {
of(s)
return
}
var i, m, hl,
dx = s.grace ? 4 : [4.7, 5, 6, 7.2, 7.5][s.head] * 1.4,
p_st = this.get_staff_tb()[s.st]
for (m = 0; m <= s.nhd; m++) {
hl = abc2svg.clair.hl_tb[s.notes[m].pit]
if (!hl)
continue
for (i = 0; i < hl.length; i++)
this.set_hl(p_st, hl[i], s.x, -dx, dx)
}
}, // draw_hl()
// draw the key signature
draw_keysig: function(of, x, s) {
if (!s.p_v.clair) {
of(x, s)
return
}
var i,
staffb = this.get_staff_tb()[s.st].y,
a_tb = new Int8Array(24),
sc = [0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23],
// major scale (2 octaves)
bn_tb = [7, 2, -3, 4, -1, 6, 1, 8, 3, -2, 5, 0, 7, 2, -3],
// #sf -> (major) base note
bn = bn_tb[s.k_sf + 7] // base note
if (!s.k_a_acc) {
if (s.k_sf > 0) {
for (i = 1; i <= s.k_sf; i++) {
a_tb[bn_tb[i] + 3] = 1; // index = y + 3
a_tb[bn_tb[i] + 12 + 3] = 1
}
if (bn + sc[s.k_mode] > 8)
bn -= 12
for (i = 7; --i >= 0; ) {
y = bn + sc[s.k_mode + i]
this.xygl(x, staffb + y * 3, (y & 1) ? "wk" : "bk");
if (a_tb[y + 3])
this.xygl(x,
staffb + y * 3,
"sht");
switch (s.k_mode + i) {
case 3:
case 7:
case 10:
x += 4.5
break
}
}
} else {
for (i = s.k_sf; i < 0; i++) {
a_tb[bn_tb[i + 6] + 3] = 1; // index = y + 3
a_tb[bn_tb[i + 6] + 12 + 3 ] = 1
}
if (bn + sc[s.k_mode] > 8)
bn -= 12
for (i = 0; i < 7; i++) { // down top
y = bn + sc[s.k_mode + i]
this.xygl(x, staffb + y * 3, (y & 1) ? "wk" : "bk");
if (a_tb[y + 3])
this.xygl(x,
staffb + y * 3,
"flt");
switch (s.k_mode + i) {
case 2:
case 6:
case 9:
x += 4.5
break
}
}
}
} else if (s.k_a_acc.length) {
for (i = 0; i < s.k_a_acc.length; i++) {
var acc = s.k_a_acc[i];
j = sc[(acc.pit + 3) % 7];
a_tb[j] = acc.acc;
a_tb[j + 12] = acc.acc
}
// todo
}
}, // draw_keysig()
// update a clef
new_clef: function(s) {
switch (s.clef_type) {
case 't':
s.clef_line = 3;
s.clefpit = -2;
s.clef_name = "cl4"
break
case 'c':
s.clef_line = 3;
s.clefpit = 6;
s.clef_name = "cl3"
break
case 'b':
s.clef_line = 3;
s.clefpit = 14;
s.clef_name = "cl2"
break
}
}, // new_clef()
// function called before SVG generation
output_music: function(of) {
var s, m, mp, p_v, v,
cfmt = this.cfmt(),
tsfirst = this.get_tsfirst(),
voice_tb = this.get_voice_tb()
// define the new clefs and key signatures
// (clefs converted from clairnote.ly)
this.do_begin_end("svg", null, '<defs>\n\
<g id="cl2">\n\
<path transform="translate(-9,-27) scale(8)" d="m0.2656 -0.78107\n\
C0.3775 -0.79547 0.4351 -0.84567 0.7003 -0.85587\n\
C0.9459 -0.86587 1.0531 -0.85987 1.1805 -0.83797\n\
C1.6967 -0.74937 2.1173 -0.13032 2.1173 0.64059\n\
C2.1173 2.10531 0.9987 3.04975 0.019 3.8078\n\
C0 3.8345 0 3.846 0 3.8652\n\
C0 3.9101 0.022 3.94 0.056 3.94\n\
C0.071 3.94 0.079 3.93904 0.107 3.9231\n\
C1.3341 3.23572 2.6095 2.2656 2.6095 0.57604\n\
C2.6095 -0.4711 2.0006 -1.05061 1.1664 -1.05061\n\
C0.9058 -1.05561 0.7658 -1.05861 0.5568 -1.02591\n\
C0.4588 -1.01061 0.248 -0.97281 0.219 -0.92831\n\
C0.165 -0.89151 0.162 -0.77308 0.266 -0.78129"/>\n\
<text x="-5" y="-24" font-size="28px"></text>\n\
</g>\n\
<g id="cl3">\n\
<path transform="translate(-9,-12) scale(8)" d="m1.0406 -2.93878\n\
C0.9606 -2.93578 0.8881 -2.93178 0.8237 -2.92878\n\
L0.8237 -2.92846\n\
C0.6586 -2.92046 0.4659 -2.89806 0.3697 -2.87906\n\
C0.1409 -2.83386 0.0236 -2.78916 0 -2.75937\n\
C-0.018 -2.73927 -0.015 -2.71087 0 -2.69037\n\
C0.023 -2.64587 0.145 -2.67017 0.4188 -2.72887\n\
C0.5108 -2.74867 0.6924 -2.76597 0.8607 -2.77257\n\
C1.0868 -2.78157 1.2883 -2.70417 1.3194 -2.69167\n\
C1.7053 -2.53668 2.0444 -2.24033 2.0444 -1.46855\n\
C2.0444 -0.8488 1.8942 -0.04261 1.4629 -0.04261\n\
C1.4489 -0.04061 1.4419 -0.03861 1.4289 -0.02891\n\
C1.4149 -0.01311 1.4179 -0.00091 1.4169 0.01179\n\
C1.4169 0.01193 1.4169 0.01195 1.4169 0.01211\n\
C1.4169 0.01225 1.4169 0.01227 1.4169 0.01243\n\
C1.4169 0.02513 1.4169 0.03723 1.4289 0.05313\n\
C1.4389 0.06213 1.4479 0.06493 1.4629 0.06683\n\
C1.8942 0.06683 2.0444 0.87302 2.0444 1.49278\n\
C2.0444 2.26455 1.7053 2.56059 1.3194 2.71559\n\
C1.2884 2.72799 1.0868 2.80579 0.8607 2.79679\n\
C0.6924 2.78979 0.5113 2.77259 0.4188 2.75279\n\
C0.145 2.69409 0.0231 2.66979 0 2.71429\n\
C-0.011 2.73479 -0.014 2.76349 0 2.78359\n\
C0.024 2.81339 0.1409 2.85799 0.3697 2.90328\n\
C0.4657 2.92228 0.6586 2.94468 0.8237 2.95268\n\
L0.8237 2.953\n\
C0.9525 2.958 1.1126 2.9714 1.305 2.96\n\
C1.9479 2.916 2.5587 2.47655 2.5587 1.48844\n\
C2.5587 0.89409 2.1807 0.20184 1.7065 0.01218\n\
C2.1807 -0.17748 2.5587 -0.86972 2.5587 -1.46406\n\
C2.5587 -2.45218 1.9479 -2.89194 1.305 -2.93594\n\
C1.209 -2.94194 1.1207 -2.94094 1.0406 -2.93794"/>\n\
<text x="-5,-5" y="0,-24" font-size="28px"></text>\n\
</g>\n\
<g id="cl4">\n\
<path transform="translate(-9,3) scale(8)" d="m1.5506 -4.76844\n\
C1.5376 -4.76844 1.5066 -4.75114 1.5136 -4.73384\n\
L1.7544 -4.17292\n\
C1.8234 -3.97367 1.8444 -3.88334 1.8444 -3.66416\n\
C1.8444 -3.16204 1.5635 -2.76967 1.2174 -2.38312\n\
L1.0789 -2.2278\n\
C0.5727 -1.68982 0 -1.16441 0 -0.45906\n\
C0 -0.36713 -0.6414 1.05 1.4549 1.05\n\
C1.5319 1.05 1.6984 1.0492 1.8799 1.0372\n\
C2.0139 1.0282 2.1594 0.9969 2.2732 0.9744\n\
C2.3771 0.9538 2.5752 0.8757 2.5752 0.8757\n\
C2.7512 0.8152 2.6612 0.62915 2.5442 0.6835\n\
C2.5442 0.6835 2.3481 0.7626 2.2449 0.7822\n\
C2.1355 0.803 1.9939 0.8319 1.8645 0.8382\n\
C1.6935 0.8462 1.5257 0.8402 1.4569 0.8352\n\
C1.1541 0.8139 0.8667 0.67432 0.6558 0.48763\n\
C0.5148 0.36284 0.3782 0.17408 0.3582 -0.12709\n\
C0.3582 -0.76471 0.792 -1.23147 1.255 -1.71365\n\
L1.3978 -1.86523\n\
C1.8046 -2.29959 2.185 -2.75829 2.185 -3.32815\n\
C2.185 -3.77846 1.9185 -4.42204 1.6113 -4.75678\n\
C1.5983 -4.76858 1.5713 -4.77188 1.5513 -4.76828"/>\n\
<text x="-3" y="0" font-size="28px"></text>\n\
</g>\n\
<ellipse id="bk" class="fill" rx="2.9" ry="2.4"/>\n\
<ellipse id="wk" class="stroke" stroke-width="1" rx="2.5" ry="2"/>\n\
<path id="flt" class="stroke" stroke-width="1.5" d="m-2.5 -1l-7 -7"/>\n\
<path id="sht" class="stroke" stroke-width="1.5" d="m-2.5 1l-7 7"/>\n\
</defs>')
// change the pitches of all notes
// (pitch + accidental => offset)
for (v = 0; v < voice_tb.length; v++) {
p_v = voice_tb[v]
if (!p_v.clair)
continue
p_v.scale = 1.3;
for (s = p_v.sym; s; s = s.next) {
if (!s.dur)
continue
for (m = 0; m <= s.nhd; m++) {
mp = s.notes[m].midi
if (mp) {
mp -= 46;
s.notes[m].pit = mp
delete s.notes[m].acc
}
}
}
}
of()
},
// set a format parameter
set_fmt: function(of, cmd, param) {
if (cmd == "clairnote") {
if (!this.get_bool(param))
return
this.set_v_param("clair", true);
this.set_v_param("stafflines", "|-|---|-|")
this.set_v_param("staffscale", .8)
return
}
of(cmd, param)
},
// set the pitches according to the clefs
set_pitch: function(of, last_s) {
if (last_s) {
of(last_s)
return // not the 1st time
}
var p_v, s, v,
voice_tb = this.get_voice_tb(),
staff_tb = this.get_staff_tb()
for (v = 0; v < voice_tb.length; v++) {
p_v = voice_tb[v]
if (!p_v.clair)
continue
abc2svg.clair.new_clef(staff_tb[p_v.st].clef)
for (s = p_v.sym; s; s = s.next) {
if (s.clef_type)
abc2svg.clair.new_clef(s)
}
}
of(last_s)
},
// set the clairnote flag in the current voice
set_vp: function(of, a) {
var i,
curvoice = this.get_curvoice()
for (i = 0; i < a.length; i++) {
if (a[i] == "clair=") {
curvoice.clair = a[i + 1]
break
}
}
of(a)
},
// set the width of the clairnote key signatures
set_width: function(of, s) {
if (s.k_sf && s.p_v && s.p_v.clair) {
s.wl = 8;
s.wr = 10
} else {
of(s)
}
},
set_hooks: function(abc) {
abc.draw_hl = abc2svg.clair.draw_hl.bind(abc, abc.draw_hl);
abc.draw_keysig = abc2svg.clair.draw_keysig.bind(abc, abc.draw_keysig);
abc.output_music = abc2svg.clair.output_music.bind(abc, abc.output_music);
abc.set_format = abc2svg.clair.set_fmt.bind(abc, abc.set_format);
abc.set_pitch = abc2svg.clair.set_pitch.bind(abc, abc.set_pitch);
abc.set_vp = abc2svg.clair.set_vp.bind(abc, abc.set_vp);
abc.set_width = abc2svg.clair.set_width.bind(abc, abc.set_width)
}
} // clair
if (!abc2svg.mhooks)
abc2svg.mhooks = {}
abc2svg.mhooks.clair = abc2svg.clair.set_hooks