Home Artists Posts Import Register

Content

Thanks for bearing with me this last month. The implementation of the macro system has been a particularly soul crushing experience but I am now starting to see some light at the end of the tunnel so I am finally happy to write an update post.

In my last post I estimated the macros system would take "at least a month". That was a little over a month ago and I feel like I'm about 75% done. However things have slowed down a little now due to the sheer difficulty of getting the last few parts working. When I work on adding big features like this the worst parts are always things that I didn't anticipate.

Some of the annoying parts I have been working on recently:

1. Send/Receive blocks

A send/receive block pair are connected together. The user selects both and generates a new macro from them.

With other block types, the new blocks inside the macro would be generated by cloning the original instances that the user selected. I think this works well for most block types and is what the user would expect to happen.

With send/receive blocks this doesn't make sense because then the receive block would be pointing to the original send block outside the macro, instead of the new send block that was created inside the macro. Therefore whenever an existing receive block is involved in macro generation, instead of cloning, a copy is created instead. The copy is then updated to point at the new source block instance that was created inside the macro.

2. Macro delete + undo

A macro block is deleted and then the user does an "undo" command to restore it.

In blockhead the rule is if an instance of a block is deleted from the workspace and there are no remaining instances (clones) to that same block, then the underlying block is deleted from memory. In order to support undo'ing the block back into existence, Blockhead checks if it is going to be deleted and generates undo data to restore it.

In the case of a macro block being deleted, Blockhead now has to go through all internal blocks inside the macro (possibly recursively since nested macros are possible) and check if they are also referenced outside the macro. If not, the underlying blocks will be deleted, and the undo data to restore those blocks has to be added to the undo data for the macro block.

3. Interaction with the baking system

This has been the largest and most difficult issue to deal with. The macro system has made it much harder to paint a clear picture of when and where items need to be baked, and what depends on what. In the next build there will probably be a little bit of redundant baking going on which I will try to clear up at a later time.

When a macro contains an effect, send, or another macro block, these are considered to be input blocks and so they should be able to process audio coming from outside the macro. As a rule blocks always wait for all their dependencies to bake before they bake themselves. In the case of input blocks inside a macro, their logical dependencies could be outside the macro.

One of the things I ended up doing to simplify things was to have a "baked input" for every macro block which internal blocks can draw audio from, so they don't need to know anything about blocks outside the macro during the baking process. They just register the macro input as a baking dependency instead.

The bouncing process is very similar to baking, but at the point that a bounce is triggered for a selection of blocks which contains a macro, macro inputs might not be baked yet, so I still also need a way to just render the entire selection without waiting for things to bake. So there's multiple systems for doing the different kinds of offline rendering.

4. Manipulators

Manipulators work upwards and downwards through the macro system, i.e. they can affect internal blocks inside a macro in the same workspace, and also blocks outside the current macro.

Colugo on Twitter: "The manipulator system works upwards and downwards through macro blocks https://t.co/IfNa4W7J3u" / Twitter

The system for searching through the workspace for intersecting blocks and applying parameter manipulations was already bad enough but now it does it through macro boundaries too. The search has to behave differently depending on the manipulator destination settings. If 'Affect Parent Blocks' is checked and 'Affect All Tracks' is unchecked it does work as expected in a situation with nested macros, i.e. it will affect blocks on the current track, and the track that contains the parent macro, and the track that contains the parent macro of the parent macro, etc.

I actually got all of this working pretty well, and then ended up deliberately breaking a lot of the manipulator system during a refactor so now I need to implement it again. But it should be straightforward now.

---

There is still a bunch to do and sometimes I get quite frustrated when I feel like I am not solving things as quickly as I could. Stepping back and looking at what I've managed to get working in a month I think it's not so bad.

Comments

No comments found for this post.