-
-
Notifications
You must be signed in to change notification settings - Fork 295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
255: Implicit Voice Group Closing #286
255: Implicit Voice Group Closing #286
Conversation
…t the end of any sequence
Thanks for the PR, this looks great overall! I'm impressed 😀 One concern I have with this approach is that we might not necessarily want to do an
Maybe the right thing to do here is to add an implicit It would be good to have a few more test cases covering different combinations of voice groups and event sequences. They're kind of confusing in that voice groups can contain event sequences, but event sequences can also contain voice groups. I'm not exactly sure what the expected behavior should be if you tried to do something like this:
(If that last example gets too complicated, we can always handle it as a separate issue... I have a feeling it might take some work to handle cases like that properly!) |
So, I tried closing voice groups at the end of a EDIT: That last example is from master, sorry, not the transitory code that was using So, new prospective change: what if the start of any voice group implicitly ends voice groups? The comments in events suggest that this shouldn't always happen, but it otherwise makes a certain intuitive sense. I was wondering if you could provide a small snippet of that expected voice-group-continuation behavior so I could make sure it works/write a test if possible. I also naturally removed the unconditional voice group closure at the end of any sequence. Totally happy to write some more tests around nested voices and event sequences; I'll add those before merge, once we have a good behavior nailed down. I do agree with the suggestion, though, that the problem of nesting voices might be better handled as a separate change. It strikes me as weird to even ask a voice to itself somehow play voices; maybe that should be a parse error? |
Was turning it over in my head a bit more. I thought, okay, what if we just end groups between any elements of a repeat? We can't, of course, in case there's a repeat inside a voice. Well, what if we just somehow...end groups that appear in the preceding element? You don't really want to do that, because the parser tracks voices by number, and it seems like you could create some pretty strange behaviors by accident. It definitely seems soluble; the problem may be my understanding how voice resolution is intended to work. For instance, what should this do?
How many beats should the resulting playback last? As of this branch, it's 9 (V1 & V2 together, then V3, then V3 again). As of master, it's 6 (V1, V2, and V3, with V3 having six total notes). Is the resolution of |
I made a gist showing an example of this in an Alda REPL and doing the same thing in a Clojure REPL in the alda.lisp namespace. In both cases, we build up the score incrementally with these steps:
The way we have things implemented now, it is important that the voice group created in step 3, does not implicitly end itself, so that step 4 can add a second voice to the same voice group. I would be open to re-thinking this a little... I wonder if it might be better to keep the voice group concept internal, so that any new voices get added to the voice group automatically? i.e, step 3 would become "Add voice 1, containing the notes C, D, E." A voice group would be implicitly created the first time a voice is encountered in a score, and the group would remain open until either:
After thinking about this a little more, I don't think we should implicitly do end-voice-group at the end of a variable definition. When you use a variable, it should feel like a simple text expansion. In other words, your example above should be equivalent to:
which, in turn, is equivalent to:
Of course, we aren't really text-expanding. We're parsing the events in a definition and storing them in the
That's what I'm thinking. It seems like something we shouldn't support because it doesn't really make sense to start a new voice "inside of" a voice. Usually when the parser encounters a Thanks for all your work on this, I really appreciate it! 🍻 |
… quite the opposite: don't wipe voice-instruments on new voice group either
No problem! The explanation makes it much clearer, thank you. It does seem like we should go basically the opposite direction and not wipe voice instrument state except on part transition or when explicitly told to. So, I removed the reset of It makes me a little nervous because I figured it was intended for something, but none of the tests failed after I did it. I tried to at least cover the bases by manually testing:
...and some random toying around based on my new understanding of voice behavior. It all seems to check out, so it's possible that that called was just outdated? Also added a couple more tests explicitly asserting empty or non-empty voice instruments at various transitions. |
Actually, interesting corollary effect: Does |
It's a nice surprise that we actually don't seem to need the "voice group initialization" stuff in the boot.user=> (update-in {:foo nil} [:foo :bar] #(if % % :baz))
{:foo {:bar :baz}}
boot.user=> (update-in *1 [:foo :bar] #(if % % :baz))
{:foo {:bar :baz}} Thankfully, Clojure is a well-designed language and already does the right thing! Nicely done, Clojure!
I had that same thought! It seems like we've moved towards voice groups being an implicit thing, so we don't technically need the I'm going to check out your branch and do some testing to make sure this change doesn't break anything. If all looks good, I think we can merge it! |
Just tested this by running tests, experimenting in the REPL, and playing all of the example scores. Everything looks/sounds good! 👍 |
I don't have much Alda knowledge, so I've probably missed some use cases. However, this at least tackles the specific problem cases in issue 255. It makes two changes that are kind of related, but wouldn't strictly have to be part of the same PR if one of them doesn't check out.
Use lookahead to allow an unclosed voice group to parse as part of an event sequence. This addresses the parse error caused by
piano: [V1: c d e V2: e f g]*2
.Unconditionally close any
:event-sequence
(including variable defs) with an:end-voice-group
. This allowsfoo = V1: c d e V2: e f g
to be multiplied byfoo*2
. From a design standpoint, though, I'm not sure if you want this behavior so broadly. The comments on the issue implied that maybe you do, but please correct me!Right now the
:end-voice-group
is unconditional, even if, for instance, the sequence already ends with:end-voice-group
or doesn't end with:voice-group
at all! I wanted to get the change out there for discussion before optimizing.I wrote some tests to cover my limited knowledge. Let me know what you think!