yeah, when you put it like that…
The difference I guess being that it attempts to compensate only for cable delays propagating through the module graph (not for, say, FFT performed by the module), but, yeah, a non-trivial problem to say the least!
EDIT: thought about this a bit (uh oh):
- Consider a scene in Rack as a digraph with modules as nodes, cables as edges, and inter-module delay as constant, positive edge weights (n=1). (Let’s assume we’re not using Little Utils Teleport or equivalent).
- This structure is highly amenable to well-understood graph algorithms. For example, the shortest weighted path between any two modules, if a path exists, can easily and efficiently be found with Dijkstra or a number of other simple graph algorithms, even when there are cycles (since the weights are always positive) and it won’t need recomputation unless there’s a cable operation (add, remove, repath).
One minimal, but useful, function, which I’d suggest as a proof-of-concept, is this:
- Allow the user to define a source module (A) and a final module (B).
- For each port on B, identify if there is a path from A to B that ends at the given port and determine its shortest weighted length. (In the graph formalism, we basically loop through the ports, temporarily delete all cables other than the one for the port, and then run Dijkstra or alternative).
- If there are two or more such ports, set port delays for each such port on B so that the sum of the path length plus the port delay for each connected port equals the longest path length minus one. So if there’s a direct connection from A to port B1, a two-hop connection from A to B2, and a five-hop connection from A to B3, set the additional delays as 4 on B1, 1 on B2, and 0 on B3.
To what end? Well, if A is an oscillator and B is a mixer, this would allow on-demand, automatic syncing up of parallel audio paths from a common source without comb filtering in cases where the parallel paths go through different numbers of modules. (Or imagine that A is a mid-side splitter and B is a mid-side combiner; I guess that’s just a specialized form of mixing, but now you don’t have to count modules in order to ensure that your different mid/side processing chains don’t get out of sync). Note that we’re not trying to eliminate or even minimize sync differences along the paths from A to B; we only care about it at the end.
The port delay adjustment be re-run manually, or possibly re-run automatically when there’s a cable change (since by definition the outcome can’t change unless there’s a cable change).
I think that would be a useful advanced feature. The adjustment algorithm could be added via module, but despite Rack’s flexibility I’m pretty sure that a module couldn’t add the per-port delays (maybe there’s some terrifying hack, but a custom build would seem to be the way to go here).
I don’t know how many more use cases there are here, or whether more advanced approaches would need to be used to deal with them. For me, the (probably impossible) hope has always been to reduce cable delay to zero EXCEPT when a single-sample delay is necessary to avoid feedback. But why? No one cares about start-to-end latency of a few samples; it’s [probably] only an issue when signals get out of sync and are then re-combined, and what I really like about @DaveVenom’s suggestion of a variable port delay is that you can compensate for that at the point of recombination without it changing the visible structure of your patch (and, if the tool-assisted graph theory trickery pans out, maybe even without manually counting and updating).