Home Artists Posts Import Register

Content

When blocks are moved around a bunch of stuff is happening behind the scenes. One of the most complex things going on is the dependency graphs.

I have been working on implementing macros and the existing implementation of this stuff was giving me a huge headache so I ended up rewriting the entire thing. So while it is fresh in my head here is a post about it.

Blockhead has to keep track of which blocks are logically connected to other blocks on the workspace. There are two different contexts where this has to happen:

1. Manipulators

Every time any block is moved, even if it's only a pixel or two, it has to update its relationships with the other blocks on the workspace.

If a manipulator block is moved, it has to check if it is now intersecting with any plugin blocks.

If it detects an intersection, it further checks if:

  • the block is a match for the manipulator destination settings (the top-right button of the manipulator block)
  • the plugin has parameters that should be manipulated. For example if the manipulator block has a Pitch manipulator and the intersecting plugin block has a Pitch parameter then the two blocks are connected together.

The connection is always performed symmetrically, so the manipulator block is added to the plugin block's list of dependencies, while the plugin block is added to the manipulator block's list of dependants.

If the plugin block was the one that moved into range of the manipulator, but the manipulator stayed still, the same logic has to sort of be performed in reverse.

Here the plugin block checks if it is intersecting with any matching manipulator and creates any appropriate connections.

Any time a block is moved (and certain other operations), it also has to iterate through all its existing connections, and check if any of them are expired.

In the case of manipulators, connections are considered expired if the two blocks no longer intersect, or if the destination setting no longer matches.

This is part of why it's necessary to maintain the connections symmetrically. When the plugin block moves we don't want to have to go through every manipulator on the workspace to check if it was connected to the block we just moved (though this is in fact what is happening in v0.25.2, so perhaps it is fine to do that. Regardless we won't be doing that any more.)


2. Baking

To save CPU during baking, a block will always wait for its dependencies to be baked first, so that it can just use the baked audio in its own baking process rather than relying on the real-time processor. Send/receive blocks also rely on the baking system to do their stuff, as long as they are not involved in a feedback loop.

Therefore the baking system needs to maintain a similar graph to the manipulation system. Here it's a bit more complicated though.

The baking graph consists of audio sources and audio sinks. An audio source is anything that emits audio whereas an audio sink is anything which receives audio.

  • The sampler block is an audio source because it emits audio but doesn't do anything with incoming audio from the lanes above it. (The choking system works as a layer on top of all this so that isn't considered.)
  • The effect block is both an audio source and an audio sink because it processes the incoming audio and also emits new audio.
  • The send block is an audio sink because it does something with the incoming audio (records it to a buffer). Counter intuitively it also counts as an audio source, but only if it is muted. That's because if it isn't muted then it is just ignored in the signal chain (the audio coming from above passes straight through to the blocks below), but if it is muted then it silences all the audio in its range, which is a manipulation on the incoming audio which should cause the blocks below to re-bake. This is another thing I fixed during the rewrite - the muting interaction doesn't currently work correctly in v0.25.2.
  • The receive block is just an audio source, however send and receive blocks both have some additional logic on top all this to further manage their dependencies, since they can also be logically connected together via the send/receive system.


In this example, assuming the send and receive blocks are connected together, a feedback loop exists. In this case baking is turned off for everything involved in the feedback loop, which here includes the send block, the receive block, and the effect block. Baking doesn't need to be turned off for the sampler block because it only injects audio into the feedback path, but doesn't rely on the output of the feedback signal.

Comments

No comments found for this post.