Dev Log #65 - Macros
Added 2022-07-05 17:19:12 +0000 UTCI've been really tired recently recovering from COVID or whatever it was, so I haven't been able to follow my usual routine. All was not lost because I spent a lot of time lying flat on my back thinking about how to implement the macro system in Blockhead. This is something I have been planning for ages and I know the implementation details are going to be really complex, not something I can just start coding away at.
A "workspace" is the main work area you see in the window when you start Blockhead. A workspace can contain tracks, each track can contain lanes, each lane can contain blocks (actually block references). A macro will be a block which contains a workspace inside it. They will be able to do a bunch of things but the main motivation is for a way to non-destructively group blocks together (for example a drum loop/phrase made up of multiple blocks). Getting at least this working first of all will be my initial focus.
A macro block can be placed on a lane just like any other block. There will be some kind of thumbnail representation of the contents:

Opening the macro block (the interface for doing that is undecided at this point) will switch the currently visible workspace to the one inside the block. I'll be experimenting with some kind of breadcrumb trail thing above the existing workspace menu which shows where you currently are in the project hierarchy (macros can contain more macros).

Inside the macro you can do whatever you want. It's just another workspace. The only difference is it will also be able to accept audio input from the level above (like an effect block). Like other blocks, macros can be cloned and the changes you make in one instance will be automatically reflected in the others.
Conceptually the cloning system in Blockhead is something like this:


Four "sampler block refs" point to the same underlying "sampler block". The distinction between "block" and "block ref" is pretty much just a UI thing. From the point of view of the audio system there is not really any such thing as cloning. The audio system just sees four blocks which each have their own copy of the data required for playback (though sample data is shared).
It might make sense that when something changes which is shared between all four instances (e.g. the base pitch of the block, before any manipulations happen), then it should only have to change that data in once place. I won't go into the gory details but there are many reasons why things can't happen that way. Instead, when some "base" block data changes, the system iterates over every instance of the block and updates the block data for all of them.
The audio thread also doesn't know anything about manipulators. Manipulation all happens UI-side so by the time the parameter data gets to the audio system it has already been manipulated and is ready to go.
For macros this cloning system doesn't really help at all because now blocks can contain workspaces with more blocks, and that is all outside the scope of "block data". I spent a lot of time trying to figure out if I can get away with having some base macro that individual instances of the same macro can refer to, but I couldn't think of any way that this could possibly work. So instead every instance is going to need its own copy of the entire macro workspace.
My current plan is, any time anything changes inside a macro block ref, it will iterate through all other macro block refs in its reference group and run the appropriate commands to synchronize them. So each block ref inside an instance of a macro will have an equivalent block ref inside every other instance of the same macro. So a simple example of "two instances of a macro block which contains one sampler block ref and nothing else" might look conceptually like this nightmare diagram:

When the sampler block ref is modified in one instance of the macro, that instance is then considered to be the "most up-to-date" version of the macro and all other instances need to be synchronized to match it. The "macro block" structure at the top also still needs to maintain some representation of the inner workspace for the purposes of saving and loading it (the macro block might just exist in the block list without any extant block refs referring to it).
I'm fully expecting to find some big problem with this design so I'm not married to it. If it does work then a nice thing is it's mainly a UI-side implementation and the audio system won't have to do anything much more complicated than what it's already doing.
Some more things I didn't talk about because my brain is already broken, but maybe in future posts:
- Macro recursion (a macro which contains itself) is theoretically possible but I probably won't be allowing it in the initial implementation because it will be extremely complicated technically
- Manipulators should be able to look inside a macro block and manipulate the parameters of blocks inside it. Which again is also probably something I will leave for later