lilypond-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: A Javascript test code for modifying ties and slurs with mouse


From: Paolo Prete
Subject: Re: A Javascript test code for modifying ties and slurs with mouse
Date: Sun, 15 Dec 2019 04:37:57 +0100

At this point, here's the complete all-in-one .ly template. Much cleaner and without risks to mess the code, for the user. In addition, it has not to be patched: just open and use the generated .svg file.
Hope it could be included in the snippets repo. Thanks all for your collaboration!


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\version "2.19.45"

JSSVGSlurTuner = #(define-void-function (body) (string?)
   (let* ((mod (resolve-module '(scm framework-svg)))
          (svg-end (module-ref mod 'svg-end #f)))
     (if (procedure? svg-end)
       (module-define! mod 'svg-end (lambda () (string-join
         (list "<script type=\"text/_javascript_\"><![CDATA["
               body "]]></script>" (svg-end)) "\n"))))))

JSSVGSlurTunerScript = #"
pixelsX = document.querySelector('svg').getAttribute('width').replace('mm', '') * 96 / 25.4
pixelsY = document.querySelector('svg').getAttribute('height').replace('mm', '') * 96 / 25.4
scaleX  = document.querySelector('svg').getAttribute('viewBox').split(' ')[2] / pixelsX
scaleY  = document.querySelector('svg').getAttribute('viewBox').split(' ')[3] / pixelsY

var slurId = 0
var currCp = null

function setCpsOnPath(path, x1, y1, x2, y2, x3, y3, x4, y4) {
   
    path.setAttribute('d', 'M ' + x1 + ',' + y1 + ' ' +
                           'C ' + x2 + ',' + y2 + ' ' +
                                  x3 + ',' + y3 + ' ' +
                                  x4 + ',' + y4)
}

function initSlur(node) {

    //already initialized
    if (node.hasAttribute('id'))
        return

    cpsCounter = 1
    lineCounter = 1

    for (var child = node.firstChild; child !== null; child = child.nextSibling) {
        if (child.nodeName == 'g') {
            for (var child2 = child.firstChild; child2 !== null; child2 = child2.nextSibling) {
                //console.log(child2.nodeName)
                if (child2.nodeName == 'circle') {
                    if (!node.hasAttribute('cp' + cpsCounter + 'x')) {
                        //TODO Ugly parsering, replace with a proper and safer one
                        node.setAttribute('cp' + cpsCounter + 'x', child2.getAttribute('transform').replace('translate(', '').split(',')[0])
                        node.setAttribute('cp' + cpsCounter + 'y', child2.getAttribute('transform').split(',')[1].trim().replace(')', ''))
                        child2.setAttribute('id', slurId + '_cp_' + cpsCounter)
                        child2.setAttribute('onmousedown', 'selectCp(this)')
                    }
                    cpsCounter++
                }

                if (child2.nodeName == 'line') {
                    child2.setAttribute('id', slurId + '_line_' + lineCounter)
                    lineCounter++
                }
            }
        }

        //remove 'transform' attribute and set abs coords
        if (child.nodeName == 'path') {
            if (child.hasAttribute('transform'))
                child.removeAttribute('transform')

            setCpsOnPath(child, node.getAttribute('cp1x'), node.getAttribute('cp1y'), node.getAttribute('cp2x'), node.getAttribute('cp2y'),
                         node.getAttribute('cp3x'), node.getAttribute('cp3y'), node.getAttribute('cp4x'), node.getAttribute('cp4y'))

            child.setAttribute('id', slurId + '_path')
            child.setAttribute('fill', 'none')
        }
    }

    node.setAttribute('id', slurId)

    coords = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    coords.setAttribute('transform', 'translate(' + node.getAttribute('cp1x') + ',' + node.getAttribute('cp1y') + ')')
    coords.setAttribute('font-size', '2')
    coords.setAttribute('class', 'lilySlurCoords')
    coords.setAttribute('id', slurId + '_coords')
    node.appendChild(coords)

    slurId++
   
}

function selectCp(cp) {

    if (!cp.hasAttribute('id'))
        return

    if (!detectLeftButton(event)) {
        event.preventDefault()
        showCoords(cp.getAttribute('id').split('_')[0])
        return
    }

    cp.setAttribute('color', 'cyan')
    currCp = cp
   
}

function unselectCp() {

    if (currCp)
        currCp.setAttribute('color', 'rgb(255, 127, 0)')

    currCp = null
}

function moveCp() {

    if (!currCp)
        return

    currCp.setAttribute('transform', 'translate(' + event.pageX * scaleX + ',' + event.pageY * scaleY + ')')

    //get the associated path
    assocSlurId = currCp.getAttribute('id').split('_')[0]
    path = document.getElementById(assocSlurId + '_path')

    xs = []
    ys = []

    for (i = 0; i < 4; i++) {
        //TODO ugly parser again!
        xs[i] = document.getElementById(assocSlurId + '_cp_' + (i + 1)).getAttribute('transform').replace('translate(', '').split(',')[0]
        ys[i] = document.getElementById(assocSlurId + '_cp_' + (i + 1)).getAttribute('transform').split(',')[1].trim().replace(')', '')
    }

    for (i = 0; i < 3; i++) {
        document.getElementById(assocSlurId + '_line_' + (i + 1)).setAttribute('transform', 'translate(' + xs[i] + ',' + ys[i] + ')')
        document.getElementById(assocSlurId + '_line_' + (i + 1)).setAttribute('x2', xs[i + 1] - xs[i])
        document.getElementById(assocSlurId + '_line_' + (i + 1)).setAttribute('y2', ys[i + 1] - ys[i])
    }

    setCpsOnPath(path, xs[0], ys[0], xs[1], ys[1], xs[2], ys[2], xs[3], ys[3])

}

function showCoords(assocSlurId) {

    xsOrig = []
    xsNew = []
    ysOrig = []
    ysNew = []

    coordsToDisplay = '\\\\shape #\\'('

    for (q = 0; q < 4; q++) {
        xsOrig[q] = document.getElementById(assocSlurId).getAttribute('cp' + (q + 1) + 'x')
        xsNew[q] = document.getElementById(assocSlurId + '_cp_' + (q + 1)).getAttribute('transform').replace('translate(', '').split(',')[0]
    }

    for (q = 0; q < 4; q++) {
        ysOrig[q] = document.getElementById(assocSlurId).getAttribute('cp' + (q + 1) + 'y')
        ysNew[q] = document.getElementById(assocSlurId + '_cp_' + (q + 1)).getAttribute('transform').split(',')[1].trim().replace(')', '')
    }

    for (q = 0; q < 4; q++) {
        diffX = (xsNew[q] - xsOrig[q]).toFixed(3)
        diffY = (ysOrig[q] - ysNew[q]).toFixed(3)

        coordsToDisplay += '(' + (+diffX) + ' . ' + (+diffY) + ') '
    }

coordsToDisplay  = coordsToDisplay.substring(0, coordsToDisplay.length -1)
    coordsToDisplay += ') ' + document.getElementById(assocSlurId).getAttribute('slurtype')

    alert(coordsToDisplay)
   
}

function detectLeftButton(evt) {

    evt = evt || window.event;
    if ('buttons' in evt) {
        return evt.buttons == 1;
    }
    var button = evt.which || evt.button;
    return button == 1;
   
}

window._oncontextmenu_ = (e) => {

    e.preventDefault();
   
}

var as = document.querySelectorAll('a')

//Remove all 'a' tags
for (var i = 0; i < as.length; i++) {
    as[i].replaceWith(...as[i].childNodes)
}

slurs = document.querySelectorAll('svg .lilySlur')

for (i = 0; i < slurs.length; i++) {
    initSlur(slurs[i])
}

document.addEventListener('mouseup', unselectCp)
document.addEventListener('mousemove', moveCp)
"

addJSSVGSlurTuner = \JSSVGSlurTuner \JSSVGSlurTunerScript

addControlPoints = #(grob-transformer 'stencil (lambda (grob orig)
  (define (draw-control-point pt)
#{ \markup \translate $pt \with-color #'(1 .7 .7) \draw-circle #0.6 #0 ##t #})
  (define (draw-control-segment pt1 pt2)
(let ((delta (cons (- (car pt2) (car pt1)) (- (cdr pt2) (cdr pt1))))) #{ \markup \translate $pt1 \with-color #'(1 .5 0) \draw-line $delta #}))
  (let* ((pts (ly:grob-property grob 'control-points))
         (dots (map (lambda (pt)
            (grob-interpret-markup grob (draw-control-point pt)))
            pts))
         (lines (map (lambda (pt1 pt2)
            (grob-interpret-markup grob (draw-control-segment pt1 pt2)))
            pts (cdr pts))))
    (ly:stencil-add
      (apply ly:stencil-add lines)
      (apply ly:stencil-add dots)
      orig))))

showControlPoints = {
    \override PhrasingSlur.stencil = #addControlPoints
    \override PhrasingSlur.output-attributes = #'((class . "lilySlur")(slurtype . "PhrasingSlur"))
    \override Slur.stencil = #addControlPoints
    \override Slur.output-attributes = #'((class . "lilySlur")(slurtype . "Slur"))
    \override Tie.stencil = #addControlPoints
    \override Tie.output-attributes = #'((class . "lilySlur")(slurtype . "Tie"))  
}

%% How it works:
%%
%% 1) Produce SVG output file(s) ( compile with -dbackend=svg )
%% 2) Open the SVG file(s) with any viewer (it works with Firefox and Chrome browsers too)
%% 3) Modify slurs/ties with the mouse by moving their control points
%% 4) Right click on one of the control points of a modified slur/tie,
%%     and copy and paste the _expression_ generated by the script to the .ly file, just before the corresponding slur/tie. Recompile.

\score
{
    {
       
        \showControlPoints

        g4_\( a'

        b'2~ |

        b'2( e''8 d'') c'4\)

        a'\( c'\) d' e'
       
    }
   
}

\addJSSVGSlurTuner

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


 

On Sun, Dec 15, 2019 at 3:54 AM Étienne Beaulé <address@hidden> wrote:
I wouldn't worry so much about overriding that define, as svg-end can
only be used at that one spot or the output file would be corrupted.

A patch that included a proper override was provided 9 years but
ignored. I'll get the relevant parts of the patch up-to-date as it is
proving potential.

Thanks for your solution; I was discussing with OP on IRC the
potential ways to bodge the feature, but came up a bit short on both
your suggestions, Aaron. Also I didn't know about those undocumented
features!

Étienne

reply via email to

[Prev in Thread] Current Thread [Next in Thread]