lilypond-user
[Top][All Lists]
Advanced

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

Re: How to catch post-events inside chords in an event listener?


From: Jean Abou Samra
Subject: Re: How to catch post-events inside chords in an event listener?
Date: Tue, 8 Feb 2022 00:14:06 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.5.0

Le 07/02/2022 à 20:17, Lukas-Fabian Moser a écrit :
Wow. Thanks. (And luckily Paris and Salzburg share a time zone.)

So here's what made me stumble: In scm/scheme-engravers.scm, we find

(define-public (event-has-articulation? event-type stream-event)
  "Is @var{event-type} in the @code{articulations} list of @var{stream-event}?"
  (if (ly:stream-event? stream-event)
      (any
       (lambda (evt-type) (eq? evt-type event-type))
       (append-map
        (lambda (art) (ly:prob-property art 'types))
        (ly:prob-property
         (ly:prob-property stream-event 'music-cause)
         'articulations)))
      #f))

written by Harm.

I wondered why this is not written as, for example:

(define-public (event-has-articulation? event-type stream-event)
  "Is @var{event-type} in the @code{articulations} list of @var{stream-event}?"
  (if (ly:stream-event? stream-event)
      (any
       (lambda (art-ev)
         (memq event-type (ly:event-property art-ev 'class)))
       (ly:event-property stream-event 'articulations))
      #f))

Or to be more precise, I made two changes only the first of which I actually understood:

1) "append-map"'ping the types of all articulations and then checking if "any" is "eq?" can certainly be simplified to checking if "any" fulfills a "memq". (Of course, this causes a 'true' return value to not being identical to #t, but that should be ok.)

2) But I wondered why Harm's code goes stream-event -> music-cause -> articulations -> 'types property instead of stream-event -> articulations -> 'class property.

Am I right now in thinking that Harm's code reads the articulations from the music objects that caused a stream event, while "my" variant only sees those articulations that survive into the stream event itself? And the difference would be that those articulations from non-chord notes for which there is a dedicated listener are removed, as David explained? (See example code below that seems to confirm this.)


That what David said and your example seems to prove it :-)
I'm not an expert in this area, I can't say much more.



And do I understand you correctly that the difference between Harm asking for 'types and me asking for 'class is just that for whatever historical reasons, the event classes of articulations-as-music is stored in 'types, whereas they are stored in 'class for articulations-as-stream-events?


About this I can say more though. What is 'types on
music does not necessarily end up being the same as
'class when the music becomes a stream event. Some
music types don't need to be applied to events even
when the respective music expression actually gets turned
into an event (unlike music expressions that never do, such
as SequentialMusic, etc.). For example, the post-event music
class (in 'types) matters to the parser during the music
processing phase, but is irrelevant to the translation
stage, and indeed it does not get carried over:

\version "2.22.1"

\new Voice \with {
  \consists #(lambda (context)
               (make-engraver
                (listeners
                 ((tie-event engraver event)
                  (ly:message "types: ~a   class: ~a"
                              (ly:music-property (ly:event-property event 'music-cause)
                                                 'types)
                              (ly:event-property event 'class))))))
}
{ c'~ }

=> types: (post-event tie-event event)   class: (tie-event music-event StreamEvent)


Conversely, general event classes like music-event are
irrelevant in music processing (non-music-events are
internal events used to keep contexts in sync; see
the definition of StreamEvent in define-event-classes.scm).

'types and 'class thus have different origins. 'types
is just written verbatim in the definition of the music
"type", in define-music-types.scm. 'class gets initialized
by converting the name of the music expression to Scheme
style (NoteEvent -> note-event), and looking that up
among the predefined event classes, which are defined
according to a hierarchy in define-event-classes.scm
(the code is Music::to_event). I didn't investigate what
the hierarchical structure is useful for.

The conclusion is that while your refactoring won't
cause a difference for current usage with duration-line-event,
it could matter in other cases, not just because of
articulations deleted when they get broadcast, but
because 'types could have entries not in 'class.



If all of this is more-or-less true, I think I'd like to add some comments explaining this (to me, quite subtle) difference in the code. And anyhow, reading lily/music-scheme.cc and lily/stream-event-scheme.cc, I seem to understand that ly:prob-property directly specialises to ly:music-property and ly:event-property for music and (stream) events, so probably one could/should use the more specialised functions in Harm's code?



It depends on what you call specializing -- technically
speaking, internal_get_property is a method that is not
virtual, so it will be the same whatever the flavor of
Prob at hand. The advantage of ly:music-property and
ly:event-property is that they express the intent of
the programmer better, and also do type-checking to
prevent mistakes.





Le 07/02/2022 à 20:46, Valentin Petzel a écrit :

Hello Lukas,


I suppose the reason why Harm makes the detour over the music-cause is because the articulations-property might be deleted. Thus to make sure we get the original full articulations-property he uses the original music instead.


Using (memq x l) sounds very reasonable. Afterall memq can be rewritten as

(any (lambda y) (eq? x y)) l). The append-map Harms uses basically just changes to order so that first we extract all classes and then we check once, which does not really bring any advantage in performance (since we have to walk through all of them anyway). If anything this append-map solutions should be less efficient, as each type needs to be visited up to twice.



Well, discussing speed of Scheme code testing for membership only
makes sense to a certain extent given that using hash sets would
be O(1) (i.e. constant-time) in the first place and in practice
much more efficient than the O(n) (where (eqv? n (length the-list)))
required to search an element in a list. It's too bad that
hash tables are so inconvenient in Guile: there aren't
many interesting primitives, printing their contents is less
straightforward, and there is no ready-made hash set structure
that does not involve a user-visible hash table mapping keys
to dummy values.

On the other hand, it is interesting to note that the version with
append-map allocates O(n) memory to create a new list, whereas
the version that just walks over the articulations uses O(1)
memory. Adding up the little bits, these kinds of patterns could
matter in the end since allocating and garbage-collecting memory
represents a significant part of the time spent in Scheme code.


Best,
Jean




reply via email to

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