jtackaberry / reaticulate

An articulation management system for REAPER
Other
101 stars 46 forks source link

Support implicit multi-channel routing/chasing #170

Open jtackaberry opened 2 years ago

jtackaberry commented 2 years ago

Based on the survey here, the majority of users feel CCs and other performance events (PB, AT, etc.) should be routed concurrently over all channels with currently active or recently active notes, for the multi-channel bank scenario.

72% of respondents felt performance events should route to the previous channel(s) following an route-altering articulation change for a period of time to shape release tails. 76% of respondents felt events should route concurrently to all channels with active notes.

More discussion and context here.

Some thoughts:

  1. "Active" needs to account for notes being held via CC64 after note-off. Consequently, there is some overlap with the antihangcc feature.
    • We'll call notes that are no longer ringing (either because there was a note-off without CC2/64/66, or because a note was captured by CC2/64/66 but a zero-value CC occurred releasing it) as "inactive"
  2. Implementing the "period of time" part may be a bit challenging. (What to call that? It's the opposite of a moratorium. Grace period? Continuation?) channel_map holds the current routing information (dstbuschan) per source channel, but this is updated immediately by articulation changes.
  3. We ultimately need to know, given a source channel, which dstbuschans have currently active notes, or recently active notes.
    • For currently active notes, the combination of active_channels_by_cc and active_dstbuschan_by_note seem to have what we need. Given a source channel, active_channels_by_cc tells us which dstbuschan has ringing notes held due to CC2/64/66, while active_dstbuschan_by_note tells us on which dstbuschan we have ringing notes due to note-on but not yet note-off. The problem with both these is that they track all 128 CCs (in the former case) or 128 notes (in the latter case). We really just need a simple true/false test for active notes, and don't want to have to iterate over all 128*2 just to determine that. So we'll want a new map for this purpose. Possibly we could map srcchan -> dstbuschan to a simple gauge, incrementing and decrementing at the same time as we manage active_channels_by_cc and active_dstbuschan_by_note
    • But we also need to continue concurrently routing to all previous dstbuschans that saw notes transition to inactive within some period of time. We could have a map that tracks srcchan -> dstbuschan -> timestamp which is updated every time a note becomes inactive. (First slot of dstbuschan is a bitmap of which buses have mapped channels as usual, so we can test this single slot to avoid looping in the common case.)
    • Then, when a performance event comes in from a source channel, we start with the dstbuschan as today, but then also check a) which dstbuschan from that src channel has active notes and b) which dstbuschan has recently inactivated notes, and, if there are any, merge_dstbuschan_arrays() them all. During the iteration in b) we could zero out dstbuschan that have a timestamp greater than the threshold.
    • If another source channel begins routing to the given dstbuschan, we immediately cease the concurrent routing period (unless there are still active notes on that dstbuschan -- i.e. this would only stop concurrent routing for the grace period/continuation).
    • These maps would be cleared on transport changes (stop->start, start->stop), and hence don't need to be serialized

Finally, how can/should this behavior be turned on and off? Global option? Per-track option? Per-bank option? Perhaps at first make it a global option, enabled by default, and solicit feedback from users which find they need to turn it off?

The duration of continuation should also be configurable. Express in beats? Milliseconds? Either?