alda-lang / alda-core

The core machinery of Alda
80 stars 26 forks source link

Nested voice issue #35

Closed elydpg closed 7 years ago

elydpg commented 7 years ago

Currently, you can have voices inside variable declarations. This is consistent with the mechanics of voices, however there is a problem when you try and nest them:

main = V1: c d e f g V2: e f g a b
side = side = c+ d+ f+ g+ a+
piano: V1: main V2: side

What ends up happening is that the side segment plays after the main segment's second voice. This may be expected behaviour, though it is slightly odd.

daveyarwood commented 7 years ago

I'm not sure what the expected behavior is here. The variable expansion looks like this:

piano:
  V1:
  V1: c d e f g
  V2: e f g a b
  V2: c+ d+ f+ g+ a+

This is a parse error because the first V1: isn't followed by any events. But if you discard the first V1: (or if we theoretically taught the parser to ignore it), then this is equal to:

piano:
  V1: c d e f g
  V2: e f g a b c+ d+ f+ g+ a+

There are some weird edge cases like this that result from the fact that voices can contain variables, and variables can also contain voices. Things would be simpler if it was only one or the other. I'm not sure how best to handle this yet, but I'm open to suggestions.

elydpg commented 7 years ago

it's not actually triggering a parser error because of the empty voice, even though it would if it were written explicitly as you outlined in the expansion. To me the most intuitive behaviour would be to allow the voices to nest, however it may not be the most practical and may lead to some confusing code. So I'm not really sure.

daveyarwood commented 7 years ago

I think it would be difficult to pull off nested voices syntactically. It would be possible if voice groups were explicitly openable and closeable, like if we had something like this imaginary voice group syntax:

piano:  voice-group {
  V1: voice-group {
    V1: c d e f g
    V2: e f g a b
  }
  V2: c+ d+ f+ g+ a+
}

Instead, what we have in Alda is "flat" voices that are all declared at the top-level and cannot be nested. To get the same effect, you can do something like this:

piano:
  V1: c d e f g
  V2: e f g a b
  V3: c+ d+ f+ g+ a+

Or, using variables like in your example:

main = V1: c d e f g V2: e f g a b
side = V3: c+ d+ f+ g+ a+

piano:
  main
  side

I would argue that this is easier to understand than nestable voices, despite being less flexible.

daveyarwood commented 7 years ago

Another option here is to use multiple pianos representing each part:

piano "main":
  V1: c d e f g
  V2: e f g a b

piano "side":
  c+ d+ f+ g+ a+
elydpg commented 7 years ago

Yep. I think the nested voice syntax looks ugly anyway and conflicts with {} indicating polyrhythms. I suppose this will be kept as is then.

daveyarwood commented 7 years ago

That's what I'm thinking. Nested voices is a useful concept, but I think it's too complex for Alda.

elydpg commented 7 years ago

Yep. Though I'm curious as to why my original example isn't throwing a parser error even when your expanded example would..

daveyarwood commented 7 years ago

It's basically a loophole.

V1: V1: is not valid because the parser is expecting a voice to contain at least one event. In other words, from the parser's perspective, a voice is defined as VN: (where N is a number) followed by at least one event.

main = V1: c d e f g V2: e f g a b
piano: V1: main

This, however, is syntactically valid because V1: main is a valid voice containing a single event, main.