Open gilbertohasnofb opened 3 years ago
Hi @gilbertohasnofb
By design. Though keep reading for thoughts about future tightening of the model.
Note that LilyPond scopes the context of LilyPond bar lines to the LilyPond score. We see that this is the case if we modify your first example to attach only a single bar line, and then look at the resulting LilyPond output:
string = r"\time 4/4 R1 R1 \time 6/4 R1 * 6/4 R1 * 6/4"
staff_1 = abjad.Staff(string)
staff_2 = abjad.Staff(string)
score = abjad.Score([staff_1, staff_2])
abjad.attach(abjad.BarLine("||"), staff_1[1])
#abjad.attach(abjad.BarLine("||"), staff_2[1])
abjad.f(score)
\new Score
<<
\new Staff
{
\time 4/4
R1
R1
\bar "||"
\time 6/4
R1 * 3/2
R1 * 3/2
}
\new Staff
{
\time 4/4
R1
R1
\time 6/4
R1 * 3/2
R1 * 3/2
}
>>
The LilyPond \bar
command appears in only one staff. But of course LilyPond engraves the double bar in both staves.
Abjad does, in fact, model this:
bar_line = abjad.BarLine("||")
bar_line.context
'Score'
Then, at attach-time Abjad checks for "contention" between two indicators that happen at the same moment but are "scoped" high enough (like at the level of the score) that they clash.
That check that Abjad is making is unmotivated in the example here (because the bar lines both "mean" the same thing: they're double bar lines). But imagine that you tried to attach a double bar in one staff and then a final bar in the other staff (at the same time). Then it's actually quite helpful that Abjad is alerting you to the fact that you're effectively trying to overwrite your own intentions.
(Even more so if you imagine setting conflicting metronome mark indicators at the same moment in different staves. Metronome marks are also scoped at the score.)
So the check will stay.
Your second example is different, however: my intention is to forbid the second example. I just haven't gotten around to it yet. I think the way it will work is this: when you go to attach a bar line to a note, rest, chord Abjad will check and see if the note, rest, chord is (ultimately) enclosed in a score. If yes, you can attach. If no, you'll get an exception.
I know on first glance all this might "feel" like restricting user functionality. But think about it for a moment or two and you'll realize the restriction is actually super helpful!
While we're at it, there's an analogous behavior with time signatures. I'll point it out here. And time signatures will also be subject to a slight behavior change when I get around to this part of the code.
Recall that time signatures are scoped to the staff context in LilyPond. This is also true in Abjad. Now, check out the difference between these two examples.
This does what you expect:
staff = abjad.Staff("c'4 d' e'")
time_signature = abjad.TimeSignature((3, 4))
abjad.attach(time_signature, staff[0])
abjad.f(staff)
\new Staff
{
\time 3/4
c'4
d'4
e'4
}
This is probably a surprise!
voice = abjad.Voice("c'4 d' e'")
time_signature = abjad.TimeSignature((3, 4))
abjad.attach(time_signature, voice[0])
abjad.f(voice)
\new Voice
{
%%% \time 3/4 %%%
c'4
d'4
e'4
}
Crazy, huh?
This is ancient code I wrote a million years ago. What's going on here is that I'm forbidding the time signature from visibly appearing in LilyPond until the Abjad user encloses the whole thing in an Abjad staff. Notice that now stuff works how you'd expect, with no commented-out time signature:
staff = abjad.Staff([voice])
abjad.f(staff)
\new Staff
{
\new Voice
{
\time 3/4
c'4
d'4
e'4
}
}
The behavior's less than ideal because it's too implicit: Abjad users can do all this stuff without realizing what's happening behind the scenes. (Quite possibly being unaware that a time signature won't show up.)
Much better is the bar line behavior you stumbled upon: at least the exception trains users into how to work with the system!
Finally to note is that there's another very important motivator to all this context-checking at attach-time. When you start using abjad.get.effective()
[which was abjad.inspect().effective
in Abjad 3.1] all this "effective indicator" stuff only works when an explicit abjad.Staff
or abjad.Score
context is present.
This works exactly as you expect:
staff = abjad.Staff("c'4 d' e'")
time_signature = abjad.TimeSignature((3, 4))
abjad.attach(time_signature, staff[0])
for note in staff:
effective_time_signature = abjad.get.effective(note, abjad.TimeSignature)
print(note, effective_time_signature)
c'4 3/4
d'4 3/4
e'4 3/4
But this doesn't:
voice = abjad.Voice("c'4 d' e'")
time_signature = abjad.TimeSignature((3, 4))
abjad.attach(time_signature, voice[0])
for note in voice:
effective_time_signature = abjad.get.effective(note, abjad.TimeSignature)
print(note, effective_time_signature)
c'4 3/4
d'4 None
e'4 None
This is basically Abjad pushing you to make sure there's an explicit abjad.Staff
present when you're working with indicators that are naturally scoped at the staff, and also that you have an explicit abjad.Score
present when you are working with indicators that are naturally scoped at the score.
When I add the generalized raise-an-exception-at-attach-time code then the behavior will be consistent, and the system will nudge you towards having explicit contexts.
Hi Trevor, thanks for the reply (super detailed as always), this is really appreciated. I understand the motivations you state, but I hope you won't mind me sharing a couple of thoughts about this.
Then it's actually quite helpful that Abjad is alerting you to the fact that you're effectively trying to overwrite your own intentions.
In my view, the main issue with this is that sometimes you are not overwriting your own intentions, but you are explicitly adding identical bar lines or time signatures to multiple staves, which allows for easier part extraction. The code below is a perfectly valid LilyPond file:
\version "2.20.0"
music_a = {c'1 \bar "||" d'1}
music_b = {e'1 \bar "||" f'1}
\score{
<<
\new Staff \with {instrumentName = "A"} \music_a
\new Staff \with {instrumentName = "B"} \music_b
>>
\layout{}
}
\score{
\new Staff \with {instrumentName = "A"} \music_a
\layout{}
}
\score{
\new Staff \with {instrumentName = "B"} \music_b
\layout{}
}
Sure, an user might enter some non-sense and get things overwritten, but is it really the job of Abjad to check for that? LilyPond doesn't even output a warning in the log when these things happen, e.g.:
\version "2.20.0"
music_a = {c'1 \bar "||" d'1}
music_b = {e'1 \bar "!" f'1} % different bar line
\score{
<<
\new Staff \with {instrumentName = "A"} \music_a
\new Staff \with {instrumentName = "B"} \music_b
>>
\layout{}
}
\score{
\new Staff \with {instrumentName = "A"} \music_a
\layout{}
}
\score{
\new Staff \with {instrumentName = "B"} \music_b
\layout{}
}
An Abjad user might try the first Abjad example in my issue, stumbled with that error message, and then assume when they write...:
staff_1 = abjad.Staff(r"\time 4/4 R1 R1 \time 6/4 R1 * 6/4 R1 * 6/4")
staff_2 = abjad.mutate(staff_1).copy()
score = abjad.Score([staff_1, staff_2])
abjad.attach(abjad.BarLine("||"), staff_1[1])
... that the bar line is added to both staves. Then when this user is ready to export parts, they might fail to notice that the bar line is in fact attached only to the first staff, i.e.:
>>> abjad.f(staff_1)
\new Staff
{
\time 4/4
R1
R1
\bar "||"
\time 6/4
R1 * 3/2
R1 * 3/2
}
>>> abjad.f(staff_2)
\new Staff
{
\time 4/4
R1
R1
\time 6/4
R1 * 3/2
R1 * 3/2
}
In the worst case scenario, this user might discover the problem only after shipping parts for the performers.
Your second example is different, however: my intention is to forbid the second example.
You might argue that the better approach in these examples would be to have a global variable for common time signatures and bar lines, but a counterargument is that LilyPond allows for this sort of approach I used in my initial example and, by limiting it, you are effectively locking users into a single mode of working. From the user point of view, LilyPond uses this very clear underpinning notion that whatever comes last in a simultaneous situation overwrites the previous entry. This is consistently applied, and makes the behaviour of the software very consistent for users.
This is sort of similar with the time signature and voice issue you mentioned. I had already stumbled upon that (I believe I initially reported it as a bug). It's a behaviour that is somewhat cryptic to new users, particularly those who happen to be longer-time LilyPond users and who know that the sort of syntax below, albeit unclear, is perfectly valid once again:
\version "2.20.0"
\new Voice {\time 3/4 c'2. d'2.}
With all that said, would you be able to show me in my simple minimal example below how to adapt the code so that I can have the double bar lines both in the full score as well as when extracting parts with the minimal amount of code?
staff_1 = abjad.Staff(r"\time 4/4 R1 R1 \time 6/4 R1 * 6/4 R1 * 6/4")
staff_2 = abjad.mutate(staff_1).copy()
abjad.attach(abjad.BarLine("||"), staff_1[1])
# abjad.attach(abjad.BarLine("||"), staff_2[1]) # commenting this out
score = abjad.Score([staff_1, staff_2])
abjad.show(score)
abjad.show(staff_1)
abjad.show(staff_2)
Many thanks!
Hi @gilbertohasnofb. There's a context
keyword you can set in abjad.attach()
!
What the final example is really wanting to do is to treat bar lines as though they're scoped to the staff instead of the score. You can do that like this:
string = r"\time 4/4 R1 R1 \time 6/4 R1 * 6/4 R1 * 6/4"
staff_1 = abjad.Staff(string)
staff_2 = abjad.Staff(string)
abjad.attach(abjad.BarLine("||"), staff_1[1], context="Staff")
abjad.attach(abjad.BarLine("||"), staff_2[1], context="Staff")
score = abjad.Score([staff_1, staff_2])
abjad.show(score)
abjad.show(staff_1)
abjad.show(staff_2)
(I replaced the call to abjad.mutate.copy()
with staves that initialize from a copy of the same string. Wherever you're able to do that sort of thing, it will always make your code faster in the end. The mutate operations are the most expensive in the system.)
Also, all your ideas here are good ones. Let's save them for a future design discussion! The general concept that we're thinking through here is probably called something like "contexted indicators." I've been looking forward to rethinking the entire concept in Abjad from top to bottom. When the time comes for that then we should remember to come back to these great observations!
Thanks a lot @trevorbaca, I will use the context
keyword!
Note for future thought on this issue: it's possible that Abjad should be extended to allow "duplicate" indicators to be attached to different leaves in a new way that Abjad currently forbids.
Consider the first note (or rest) of each of the (probably) four voices in a string quartet. These four notes all begin at the same offset (zero), and these four notes all live in different contexts (primary music voices of violin 1, violin 2, viola, cello). When attaching the first abjad.MetronomeMark
in the score, to which leaf should the metronome mark be attached? My usual solution to this is to use a global context (filled with skips) specifically to hold time signature and tempo information. But what if a user doesn't include a global context (or similar) in their score? There's then a decision to be made: should the first metronome mark of the piece attach to the first leaf of v1, v2, va or vc? The decision feels arbitrary: why attach a score-contexted indicator like abjad.MetronomeMark
to a note in v1 as opposed to vc? The decision also feels 'unbalanced': why attach a score-contexted indicator like abjad.MetronomeMark
to only 1 note at offset zero (instead of all 4 such notes)? And, more importantly, beyond these feelings of arbitrariness or asymmetry is the question of what should happen during the creation of parts: if the first metronome mark of the piece is attached (only) to the first note of v1, then what happens when music for vc is extracted to create the cello part?Will the first metronome mark of the piece be found? Or not? The situation probably forces users into something that functions like a global context.
Perhaps Abjad should instead allow the first metronome mark of the piece to be attached to as many different leaves at offset zero as the composer would like. This would presumably ease the construction of parts considerably, because metronome mark information could be attached to the first note in each part. The behind-the-scenes code in Abjad that determines effective indicators would have to be taught about this, but it would work. And the only constraint that users would have to follow is that multiple metronome marks attached to different leaves that all start at the same offset must all compare equal: if the starting tempo is q=66 then q=66 can be attached to as many leaves at offset zero as the user likes, but attaching q=44 to the viola would still be disallowed.
When dealing with multiple staves that belong to an
abjad.Score
, attempting to attach a bar line to more than one stave at a same given bar will result in an exception:The code above produces:
Attaching the barlines to the same positions but before adding the staves to a score will not result in an exception:
Is the first case happening by design or is that a bug? It seems odd that once a staff is part of a score it cannot receive barlines if another staff already has one at that given bar.