Will my scheme function work have unintended consequences? Adding staccatos to notes.

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

Will my scheme function work have unintended consequences? Adding staccatos to notes.

Peter Engelbert
Hello all,

I've written the following functions to add staccatos to an arbitrary number of notes:

===addStaccatos.ly=
\version "2.19.82"

#(define (add-staccato note-event)
  (set! (ly:music-property note-event 'articulations)
   (cons (make-music 'ArticulationEvent 'articulation-type "staccato")
    (ly:music-property note-event 'articulations))))

addStaccatos =
#(define-music-function (music) (ly:music?)
   (map add-staccato (ly:music-property music 'elements)) music)
===================

Here is the MWE I've used to test it:

=====test.ly=======
\version "2.19.82"
\include "addStaccatos.ly"

{ c4 \addStaccatos { c4 c4 c4 | c4^> } }
===================

I am just checking, with more experienced eyes, whether or not this function will work in all cases.  It seems to work in my minimal example, but I really don't know very much about how scheme represents music, so I'm concerned that there might be some cases where this will not work.

Also, I tried a few earlier attempts at this before getting it to work, one of them used the MUSIC-MAP function.  What does MUSIC-MAP do, and how does it differ from the standard MAP? And most importantly, what is its proper syntax? I looked it up in the .scm file where it's defined, but it's a bit beyond me at the moment.

Many thanks for your help,
Peter



_______________________________________________
lilypond-user mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/lilypond-user
Reply | Threaded
Open this post in threaded view
|

Re: Will my scheme function work have unintended consequences? Adding staccatos to notes.

David Kastrup
Peter Engelbert <[hidden email]> writes:

> Hello all,
>
> I've written the following functions to add staccatos to an arbitrary number of notes:
>
> ===addStaccatos.ly=
> \version "2.19.82"
>
> #(define (add-staccato note-event)
>   (set! (ly:music-property note-event 'articulations)
>    (cons (make-music 'ArticulationEvent 'articulation-type "staccato")
>     (ly:music-property note-event 'articulations))))
>
> addStaccatos =
> #(define-music-function (music) (ly:music?)
>    (map add-staccato (ly:music-property music 'elements)) music)
> ===================
>
> Here is the MWE I've used to test it:
>
> =====test.ly=======
> \version "2.19.82"
> \include "addStaccatos.ly"
>
> { c4 \addStaccatos { c4 c4 c4 | c4^> } }
> ===================
>
> I am just checking, with more experienced eyes, whether or not this
> function will work in all cases.
No.

> It seems to work in my minimal example, but I really don't know very
> much about how scheme represents music, so I'm concerned that there
> might be some cases where this will not work.

Music can be structured in a large number of ways.

> Also, I tried a few earlier attempts at this before getting it to
> work, one of them used the MUSIC-MAP function.  What does MUSIC-MAP
> do,

Apply to all contained music.  Not what you want here.

> and how does it differ from the standard MAP?

It descends into music rather than a list.  And at all levels.

> And most importantly, what is its proper syntax? I looked it up in the
> .scm file where it's defined, but it's a bit beyond me at the moment.

I think this is rather a case for map-some-music where you can stop the
recursion at appropriate levels.


\version "2.19.82"

#(define (add-staccato event prop)
   (set! (ly:music-property event prop)
         (append
          (ly:music-property event prop)
          (list
           (make-music 'ArticulationEvent 'articulation-type "staccato"))))
   event)

addStaccatos =
#(define-music-function (music) (ly:music?)
   (define note? (music-type-predicate 'note-event))
   (define chord? (music-type-predicate 'event-chord))
   (define rhythmic-event? (music-type-predicate 'rhythmic-event))
   (map-some-music
    (lambda (m)
      (cond ((note? m) (add-staccato m 'articulations))
            ((chord? m)
             (if (or (any note? (ly:music-property m 'elements))
                     (ly:duration? (ly:music-property m 'duration)))
                 (add-staccato m 'elements)
                 m))
            ((rhythmic-event? m) m)
            (else #f)))
    music))

%=====test.ly=======
%\version "2.19.82"
%\include "addStaccatos.ly"

{ c4 \addStaccatos { c4 c4 <c e>4 | c4^> } }
%===================


Note that recursion is stopped at rhythmic events, single note events
get staccato added (at their end) in 'articulations, chords containing
note events (or being a repeat chord, notable by having a duration) also
get a staccato.  There are a number of code remnants using event-chord
as a useless wrapper for non-chords, so I don't just embellish chords
unconditionally here.  Most of the time, you should be fine just
checking for event-chord.  But you asked about a general robust
solution, so...

--
David Kastrup

_______________________________________________
lilypond-user mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/lilypond-user
Reply | Threaded
Open this post in threaded view
|

Re: Will my scheme function work have unintended consequences? Adding staccatos to notes.

Peter Engelbert
In reply to this post by Peter Engelbert

My apologies for the rookie mistake of initially responding indiviually rather than to the list.  I have posted this to the list in case you have any more insights you want to share and so that others can learn from your response.  I have a few more questions, below--I tend toward wanting complete understanding of how things work, so I hope you will humor me.

On Fri, Sep 14, 2018 at 11:18:11PM +0200, David Kastrup wrote:

> Peter Engelbert <[hidden email]> writes:
>
> > Here is the MWE I've used to test it:
> >
> > =====test.ly=======
> > \version "2.19.82"
> > \include "addStaccatos.ly"
> >
> > { c4 \addStaccatos { c4 c4 c4 | c4^> } }
> > ===================
> >
> > I am just checking, with more experienced eyes, whether or not this
> > function will work in all cases.
>
> No.

Thank you -- I appreciate your help.  I have found learning scheme to be quite difficult compared to other languages I've learned, and even after learning a great deal I am struggling with the lilypond-specific aspects of it.

I am wondering if you might be able to supply me with a test case where my function will not work as intended?  It will help me a lot as I continue to learn, and it will also help me understand why your function works and mine doesn't.

>
> > It seems to work in my minimal example, but I really don't know very
> > much about how scheme represents music, so I'm concerned that there
> > might be some cases where this will not work.
>
> Music can be structured in a large number of ways.
>

Any advice as to how I might deepen my understanding of how music is represented by scheme?  I have read the small amount on the topic in the lilypond 'extending' manual, but I am having trouble finding any resources beyond that.

> > Also, I tried a few earlier attempts at this before getting it to
> > work, one of them used the MUSIC-MAP function.  What does MUSIC-MAP
> > do,
>
> Apply to all contained music.  Not what you want here.
>
> > and how does it differ from the standard MAP?
>
> It descends into music rather than a list.  And at all levels.
>

By 'at all levels', I am assuming you mean the varying structural levels of the complext lists that comprise Scheme's representation of music?  How might those various levels be understood?

> > And most importantly, what is its proper syntax? I looked it up in the
> > .scm file where it's defined, but it's a bit beyond me at the moment.
>
> I think this is rather a case for map-some-music where you can stop the
> recursion at appropriate levels.
>

How do map-some-music and music-map differ? Is it not possible to stop the recursion with music-map? When would it be appropriate to actually use music-map?

> \version "2.19.82"
>
> #(define (add-staccato event prop)
>    (set! (ly:music-property event prop)
> (append
>  (ly:music-property event prop)
>  (list
>   (make-music 'ArticulationEvent 'articulation-type "staccato"))))
>    event)
>

Is there a large difference between using append vs cons here? Just curious.

> addStaccatos =
> #(define-music-function (music) (ly:music?)
>    (define note? (music-type-predicate 'note-event))
>    (define chord? (music-type-predicate 'event-chord))
>    (define rhythmic-event? (music-type-predicate 'rhythmic-event))

I gather that you are defining various predicates for convenience in this function.

>    (map-some-music
>     (lambda (m)
>       (cond ((note? m) (add-staccato m 'articulations))

If we are encountering a note, then add staccato, I understand so far.

>    ((chord? m)
>     (if (or (any note? (ly:music-property m 'elements))
>     (ly:duration? (ly:music-property m 'duration)))
> (add-staccato m 'elements)
> m))

If it's a chord we're encountering, and there are either a. any notes in the 'elements list or b. the chord has a proper duration, then add staccato to the 'elements list (and not to the 'articulations list).

I am assuming this has to do with how chords are represented in scheme, since the chord as a whole possesses a staccato marking, not the individual notes.

>    ((rhythmic-event? m) m)

If we encounter a rhythmic event, which is neither a not nor a chord, leave it alone.

>    (else #f)))

Otherwise, we have encountered something other than a note, chord, or rhythmic event.  But why return false here?

>
> Note that recursion is stopped at rhythmic events, single note events
> get staccato added (at their end) in 'articulations, chords containing
> note events (or being a repeat chord, notable by having a duration) also
> get a staccato.  There are a number of code remnants using event-chord
> as a useless wrapper for non-chords, so I don't just embellish chords
> unconditionally here.  Most of the time, you should be fine just
> checking for event-chord.  But you asked about a general robust
> solution, so...
>
> --
> David Kastrup

I missed this paragraph the first time around -- thank you very much.  This is very specific and helpful. I now understand that returning false is a way to stop the rrecursion of map-some-music.

Once again, thank you for responding and for your help.  Like I said, I find this difficult and I think the best way for me to learn is to ask detailed questions to the experts on this list.  Be well, and have a good weekend!

Peter

_______________________________________________
lilypond-user mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/lilypond-user
Reply | Threaded
Open this post in threaded view
|

Re: Will my scheme function work have unintended consequences? Adding staccatos to notes.

David Kastrup
Peter Engelbert <[hidden email]> writes:

> My apologies for the rookie mistake of initially responding
> indiviually rather than to the list.

Happens.  When I notice, I usually let the post sit a while before
deciding what to do about it.  When I don't notice, I tend to be
somewhat annoyed when I put in significant work into a reply intended to
be useful for a single person.

> I have posted this to the list in case you have any more insights you
> want to share and so that others can learn from your response.

Yup, that's the idea.

> On Fri, Sep 14, 2018 at 11:18:11PM +0200, David Kastrup wrote:
>> Peter Engelbert <[hidden email]> writes:
>>
>> > Here is the MWE I've used to test it:
>> >
>> > =====test.ly=======
>> > \version "2.19.82"
>> > \include "addStaccatos.ly"
>> >
>> > { c4 \addStaccatos { c4 c4 c4 | c4^> } }
>> > ===================
>> >
>> > I am just checking, with more experienced eyes, whether or not this
>> > function will work in all cases.
>>
>> No.
>
> Thank you -- I appreciate your help.  I have found learning scheme to
> be quite difficult compared to other languages I've learned, and even
> after learning a great deal I am struggling with the lilypond-specific
> aspects of it.

Well, Scheme is in the Lisp family.  One thing to understand is that
Lisp doesn't have a programming language.  Its principal (and originally
only) data structure is the list.  Lists have an input syntax (parens
around list members) and list members can be lists or atoms, with atoms
being (originally) symbols or numbers.

With machine language, you directly speak with the underlying machine,
entering its machine code.  With Lisp, you directly speak with the
underlying compiler or interpreter, entering its parse trees.

The integration with LilyPond is sort of a chimera, with LilyPond having
its own bonafide syntax/language and data structures and processing so
that Scheme is actually more than just a direct path to LilyPond's
"compiler" and its data structures but has the flavor of a language of
its own.  It's actually been just

commit 2b5668b7afafe5939eb047e351adddf44bc40bb5
Author: David Kastrup <[hidden email]>
Date:   Thu Sep 6 16:00:48 2012 +0200

    Issue 2815: parser/lexer: de-unionize semantic values, make all of them SCM
   
    Being able to reliably interpret semantic values allows for better
    parser extension and debugging.

since the parser exclusively works with Scheme-represented data
structures internally.

> I am wondering if you might be able to supply me with a test case
> where my function will not work as intended?  It will help me a lot as
> I continue to learn, and it will also help me understand why your
> function works and mine doesn't.

Basically you can just use everything I mentioned.  You can add another
level of { } to create a deeper nested structure, you can use q as a
repeat chord, you can use \newSpacingSection as something that uses an
event-chord without containing a note (this is likely going to change,
it's an oversight), you can use chords like <c e> to check for chords
getting staccato.

> Any advice as to how I might deepen my understanding of how music is
> represented by scheme?  I have read the small amount on the topic in
> the lilypond 'extending' manual, but I am having trouble finding any
> resources beyond that.

Add a few \displayMusic incantations to select music expressions.

>> > Also, I tried a few earlier attempts at this before getting it to
>> > work, one of them used the MUSIC-MAP function.  What does MUSIC-MAP
>> > do,
>>
>> Apply to all contained music.  Not what you want here.
>>
>> > and how does it differ from the standard MAP?
>>
>> It descends into music rather than a list.  And at all levels.
>
> By 'at all levels', I am assuming you mean the varying structural
> levels of the complext lists that comprise Scheme's representation of
> music?  How might those various levels be understood?

There is no inherent meaning to "various levels".  Some music
expressions contain other music expressions as their parts, for example
sequential music has music expressions as its elements.  Which can, in
turn, be sequential music expressions.

>> > And most importantly, what is its proper syntax? I looked it up in
>> > the .scm file where it's defined, but it's a bit beyond me at the
>> > moment.
>>
>> I think this is rather a case for map-some-music where you can stop the
>> recursion at appropriate levels.
>
> How do map-some-music and music-map differ? Is it not possible to stop
> the recursion with music-map? When would it be appropriate to actually
> use music-map?

When you want to descend to all levels.  Most of the time, I prefer
using one of the better controllable functions in
scm/music-functions.scm but they are quite more recent than music-map.

>> \version "2.19.82"
>>
>> #(define (add-staccato event prop)
>>    (set! (ly:music-property event prop)
>> (append
>>  (ly:music-property event prop)
>>  (list
>>   (make-music 'ArticulationEvent 'articulation-type "staccato"))))
>>    event)
>>
>
> Is there a large difference between using append vs cons here? Just
> curious.

Appending in front vs appending at the back.  It doesn't make much of a
difference for note articulations but chords become awkward when
articulations precede the notes.  Some engravers might not deal well
with that.

>> addStaccatos =
>> #(define-music-function (music) (ly:music?)
>>    (define note? (music-type-predicate 'note-event))
>>    (define chord? (music-type-predicate 'event-chord))
>>    (define rhythmic-event? (music-type-predicate 'rhythmic-event))
>
> I gather that you are defining various predicates for convenience in
> this function.

Yes.

>>    (map-some-music
>>     (lambda (m)
>>       (cond ((note? m) (add-staccato m 'articulations))
>
> If we are encountering a note, then add staccato, I understand so far.
>
>>    ((chord? m)
>>     (if (or (any note? (ly:music-property m 'elements))
>>     (ly:duration? (ly:music-property m 'duration)))
>> (add-staccato m 'elements)
>> m))
>
> If it's a chord we're encountering, and there are either a. any notes
> in the 'elements list or b. the chord has a proper duration, then add
> staccato to the 'elements list (and not to the 'articulations list).

Very good.

> I am assuming this has to do with how chords are represented in
> scheme, since the chord as a whole possesses a staccato marking, not
> the individual notes.

It's historic baggage that articulations on chords are stored
identically to the notes itself in "elements".  It would make more sense
to put them in "articulations" instead and most of LilyPond by now will
be prepared to deal with that just as well, but at least external code
trying to interpret expressions might be confused.

Changing internals tends to cause disruptions.  Issue 2240 was such a
disruption and there were a number of benefits offsetting this
disruption that could not have been attained otherwise.

Moving chord articulations, in contrast, does not presently promise
making stuff _possible_ at all, it merely would make things less
complicated and more natural.

>>    ((rhythmic-event? m) m)
>
> If we encounter a rhythmic event, which is neither a not nor a chord,
> leave it alone.

This is just an optimization to avoid recursing into rests and similar.
I cannot imagine a case where it would change the result.

>>    (else #f)))
>
> Otherwise, we have encountered something other than a note, chord, or
> rhythmic event.  But why return false here?

Because of the way map-some-music is defined.  If we take a look at its
documentation string, it states:

(define-public (map-some-music map? music)
  "Walk through @var{music}, transform all elements calling @var{map?}
and only recurse if this returns @code{#f}.  @code{elements} or
@code{articulations} that are not music expressions are discarded:
this allows some amount of filtering.

@code{map-some-music} may overwrite the original @var{music}."

So basically this is the value we need in order to get recursive descent
in the first place.

>> Note that recursion is stopped at rhythmic events, single note events
>> get staccato added (at their end) in 'articulations, chords
>> containing note events (or being a repeat chord, notable by having a
>> duration) also get a staccato.  There are a number of code remnants
>> using event-chord as a useless wrapper for non-chords, so I don't
>> just embellish chords unconditionally here.  Most of the time, you
>> should be fine just checking for event-chord.  But you asked about a
>> general robust solution, so...
>
> I missed this paragraph the first time around -- thank you very much.
> This is very specific and helpful. I now understand that returning
> false is a way to stop the rrecursion of map-some-music.

Yes.

> Once again, thank you for responding and for your help.  Like I said,
> I find this difficult and I think the best way for me to learn is to
> ask detailed questions to the experts on this list.  Be well, and have
> a good weekend!

I'll try.

--
David Kastrup

_______________________________________________
lilypond-user mailing list
[hidden email]
https://lists.gnu.org/mailman/listinfo/lilypond-user