alda-lang / alda

A music programming language for musicians. :notes:
https://alda.io
Eclipse Public License 2.0
5.62k stars 288 forks source link

Allow multiple parts inside of an event sequence / variable definition? #323

Closed daveyarwood closed 2 years ago

daveyarwood commented 7 years ago

This would allow composers the flexibility to define whole "parts" of the song that include multiple simultaneous instruments, e.g.

verse = [
  piano: c d e f g a b c
  guitar: c c g g c
]

chorus = [
  percussion: c e c e c e c e
  piano: a f g e a f g e
]

verse verse chorus chorus

Interestingly, as @aengelberg pointed out, it is already possible to get this behavior via a hack:

verse = [(alda-code "piano: c d e f g guitar: e f g a")]
piano: verse verse

...which suggests it should be feasible to make changes to the parser to allow doing this directly.


Open question: is this fixed by default with the new parser ( alda-lang/alda-core#37 ) implementation (unreleased)?

elydpg commented 7 years ago

Hm... to me this seems a little bit inconsistent... after all instruments are the environments in which event sequences are played. Though it would be an interesting feature; one would just have to work out the mechanics in order to avoid something like this

two_instrument_score = [piano: c d e f g guitar: c d e f g]
piano: two_instrument_score

or perhaps more problematically

two_instrument_score = [piano: c d e f g guitar: c d e f g]
violin: two_instrument_score
daveyarwood commented 7 years ago
two_instrument_score = [piano: c d e f g guitar: c d e f g]
two_instrument_score
two_instrument_score = [piano: c d e f g guitar: c d e f g]
violin: two_instrument_score

It looks like with Alda 1.0.0-rc57, the parser accepts both of these scores ( 🏆 ), and they sound the same when you play them.

What's really happening under the hood is that strings like piano: and guitar: correspond to a "switch to that instrument" event. So in the case of the second example, the parser parses out the following sequence of events:

I'll admit that the score looks a little strange, and it might be slightly misleading -- for example, one might expect that adding additional notes at the end of the second score would result in them being played on violin, but instead, the current instrument is still the guitar.

I'm open to adding some additional logic to the way this works, if it's what the people want™ and it's not too difficult to implement.


Another issue:

verse = [
  piano: c d e f g a b c
  guitar: c c g g c
]

chorus = [
  percussion: c e c e c e c e
  piano: a f g e a f g e
]

verse verse chorus chorus

One might expect the chorus part not to start until the verse part finishes twice. Instead, the percussion part starts immediately because this score is equivalent to:

piano: c d e f g a b c
guitar: c c g g c
piano: c d e f g a b c
guitar: c c g g c
percussion: c e c e c e c e
piano: a f g e a f g e
percussion: c e c e c e c e
piano: a f g e a f g e

So there is a caveat with organizing your score this way, by sections. The score compiler still expects you to keep the parts in sync, e.g. by adding rests to make sure that all of the parts are the same length:

verse = [
  piano: c4 d e f | g a b > c <
  guitar: c4 c g g | c r2.
  percussion: r1~1
]

chorus = [
  percussion: c4 e c e | c e c e
  piano: a4 f g e | a f g e
]

verse verse chorus chorus

I can see this being a little cumbersome to keep up with -- it might be nice if we could find a reasonable way to keep the parts in sync automatically, without breaking the way variables work in general.

elydpg commented 7 years ago

I'm curious as to how variables are currently defined right now, since I always understood them to be event sequences, but if instrument calls are allowed in variable declarations then it's more like variables are like a sort of code injection, which seems to be what's going on here. I'm not sure if this is a better way of handling variables, but either way, it's something that needs work on.

daveyarwood commented 7 years ago

The missing piece of info there is that an instrument call is simply an event that says "switch to this instrument."

A variable is indeed a sequence of events, and with the new parser in 1.0.0-rc57, those events can now include a "change part" event, i.e. an instrument call.

daveyarwood commented 7 years ago

Moving this issue back into the "Discussion" column. It's essentially fixed as of 1.0.0-rc57, but it's not 100% intuitive how to write scores this way, since you have to make sure the parts are all synchronized. I'm curious if we can improve the experience somehow.

truj commented 4 years ago

Maybe a synchronize command like this one would make sense. I use it all the time when writing Midica files. Than you could use something like this: verse verse * chorus chorus Or maybe: verse verse @* chorus chorus ... to stay more similar to the marker syntax. Or: verse verse @! chorus chorus ... to remind to global attributes as this synchronizes ALL instruments.

daveyarwood commented 4 years ago

I think I would want a "synchronize" function/operator to be more like a scope, almost like Java's synchronized keyword:

// other synchronized threads have to wait until I'm done with this
synchronized(someObject) {
    doSomethingWith(someObject);
}

// now other synchronized threads can do their thing

Here's a half-baked idea: what if Alda had something similar that allowed you to specify that all of the parts inside of a block of code should be "in sync" at the end of the block?

I'm not tied to this syntax, but imagine if we had start-sync! and end-sync! functions to specify where the synchronized block starts and ends, e.g. this:

(start-sync!)
clarinet: c8 d e f g a b > c
flute: e4
(end-sync!)

clarinet: e1
flute: g1

would be equivalent to this:

clarinet: c8 d e f g a b > c e1
flute: e4 r2. g1

Inside of the "sync block," the clarinet part is much longer than the flute part, but the first notes of each part right after the sync block are in sync.

(This is actually very similar to how voice groups work. After you use V0: to end a voice group, the current offset becomes the final offset of the longest voice -- in other words, you pick up where the longest voice left off.)

Thinking this example through a little more, imagine if we wanted to define verse and chorus variables that contain synchronized parts, without having to ensure that all of the instrument parts are the same length. We could maybe do something like this:

verse = [
  (start-sync!)
  clarinet: c8 d e f g a b > c
  flute: e4
  (end-sync!)
]

chorus = [
  (start-sync!)
  # the clarinet part has no notes in the chorus, but because it's declared within
  # the sync block, it will end up at the same offset as the flute afterwards
  clarinet:
  flute: f+16 g f+ g f+2
  (end-sync!)
]

verse chorus verse verse chorus

I think it would be important to do this in a way that allows you to specify which parts are to be synchronized. That way, you could, for example, write a percussion part entirely separate from the clarinet and flute parts, and not be bound to the same rigid verse/chorus structure.

I'm not totally sure if the above would work, syntactically, but just an idea worth considering. Would love to hear feedback from interested parties.

daveyarwood commented 4 years ago

This feels like it might be useful enough that I would break my usual "don't add additional syntax, provide Lisp functions instead" rule and be on board with adding syntactic sugar. Like perhaps something like this:

verse = sync[
  clarinet: ...
  flute: ...
]

...could be syntactic sugar for this:

verse = [
  (start-sync!)
  clarinet: ...
  flute: ...
  (end-sync!)
]
UlyssesZh commented 3 years ago
two_instrument_score = [piano: c d e f g guitar: c d e f g]
two_instrument_score
two_instrument_score = [piano: c d e f g guitar: c d e f g]
violin: two_instrument_score

It looks like with Alda 1.0.0-rc57, the parser accepts both of these scores ( 🏆 ), and they sound the same when you play them.

This feature remains until Alda 1.5.0. It was removed in Alda 2 without proper documentation (maybe). Shouldn't that appear in the Alda 2 migration guide?

daveyarwood commented 3 years ago

Whoops! That wasn't a deliberate removal. I forgot that Alda 1 would let you include instrument calls inside of an event sequence.

Thinking about it more now, I find it confusing, and I think it might over-complicate things for now if I attempted to add it back.

I would like to come up with a good solution to the synchronization problem discussed above, and maybe that will point us in the right direction when it comes to variable definitions that include multiple parts. I'm beginning to wonder if this should be a different kind of variable, because right now, variables in Alda are interpreted within the context of a part, and I think maybe it should stay that way.