Home Artists Posts Import Register

Content

One of the last-minute bugs while trying to get v0.26.0 out the door is that manipulators are not being applied in the right order when macros are involved.

The way that manipulators are combined follows a simple set of rules. They are applied to target parameters top-to-bottom, so if a manipulator B appears below manipulator A on the workspace, manipulator A is applied to the target parameter first, and then manipulator B is applied on top of it.

The only other factor to be aware of is that manipulators can work in one of two modes: Override and Offset.

Here is an overview of how manipulators work:


Here, the first manipulator overrides the target parameter (Pitch) by applying a ramp up from 0 to 12 semitones. The bottom manipulator is in offset mode so halfway up the ramp of the first manipulator, the pitch is stepped down by an octave. At the top of the ramp, the final pitch manipulation is effectively 0 because it's been overridden to +12, then offset by -12 to get back to zero. When the first manipulator ends the second manipulator continues for a bit, so the pitch steps down to -12 for the remainder of the second manipulator.


Here, the second manipulator is also set to override mode, so the final pitch modulation is simpler: The pitch is manipulated halfway up the ramp and then the bottom manipulator kicks in and the pitch is overridden to exactly -12 for the remainder of the bottom manipulator.

By default, manipulators only affect parameters on blocks within the same track, but you can click the top-right button and enable "Affect All Tracks" and then they work across the entire workspace. The top-to-bottom rule is the same.


In v0.26.0 I am introducing macro blocks. Every macro contains its own sub-workspace. Macros can be nested (macros inside macros).

Manipulators work through macros, so you can have:

  • A manipulator affecting a parameter on the same workspace (what we have now)
  • A manipulator inside a macro affecting a parameter that is outside (at a level above) that macro
  • A manipulator outside (at a level above) a macro affecting a parameter that is inside that macro
  • A manipulator inside a macro affecting a parameter at a deeper level of the same macro
  • A manipulator inside a macro affecting a parameter that exists in a completely different macro (i.e. one macro is not a parent of the other)

You can still have multiple manipulators acting on the same parameter, and ultimately the rule of what order the manipulators should be applied is the same: top-to-bottom. From the user perspective I think what "top-to-bottom" means is quite intuitive, but in code it's not actually very simple to figure out whether one manipulator is logically above another one.


Case 1


This is the best-case scenario. Manipulator (A) is above Manipulator (B). Both manipulators are on the same track of the same workspace. Figuring out which manipulator is above the other is simple. (A) is on lane 1 and (B) is on lane 2. 1 is less than 2 therefore (A) is above.


Case 2


Here (A) and (B) are on separate tracks of the same workspace. This is only a tiny bit more work. Maybe the configuration of lanes is something like this:


We can number the lanes across the entire workspace depending on how the tracks are laid out. 1 is less than 5 therefore (A) is above (B).

Now we get to macros.


Case 3


(B) is inside a macro. The macro is on the same workspace as (A), somewhere logically below it. Looking at the diagram we can obviously see that (A) is logically above (B) but the computer doesn't magically know that. In C++ world what I have is just two references to two blocks and I need some function which figures out whether A is above B and returns true if it is:


I'm writing this function now. I'm writing the blog post mainly to try to clear things up in my own mind. In the example above, I start by detecting which block is at a deeper "macro level". This is basically the number of nested macros the block resides in, or the number of levels we have to search up through to get to the top-level, non-macro workspace at the top of the project.

If we imagine that (A) and the macro reside on a top-level workspace, (A) has a macro level of 0 and (B) has a macro level of 1.

Starting with (B), we can traverse upwards until we get to the same workspace as (A). Then we can compare the lane indices as usual to figure out that (A) is above the macro containing (B), and therefore (A) is above (B).


Case 4



Maybe (A) and the macro containing (B) aren't sitting on a top-level workspace. They might be inside another macro! The logic is the same though. (A) has a macro level of 1 and (B) has a macro level of 2. So we start with (B) and search upwards until we find (A)'s workspace. In this case it's the sub-workspace of a containing macro block. We stop searching upwards there and compare lane indices as usual.


Case 5



After a shot of rum we are ready to tackle this. (A) has a macro level of 1 and (B) has a macro level of 2, so we start with (B) and begin searching upwards through the macros until we find (A)'s workspace. Except we never find it because (A) is inside a different macro. So we get all the way to the top-level workspace and make a note of what lane the the top-most macro was on. Then we do the same with (A): search upwards until we get to the top-level workspace and see which top-level lane we are on, compare the lane indices in the usual way to get the answer.


Case 6



Here (A) and (B) have the same macro level so we don't know which one to start with. It doesn't matter though, we can just pick one to start with and run the same algorithm.


Case 7



Oh dear. This ruins everything. We can start with (B) and search upwards until we get to the top, and make a note of the lane we are on. Then we do the same with (A) and end up on the exact same lane! Effectively we should be stopping at the first macro level and comparing indices there, but there was nothing telling us that we should stop there.

Obviously we could just start with both (A) and (B) and search upwards with them both together, one level at a time and check each step of the way to see if we are on the same workspace. So let's ruin things even more:


Case 8


I think this is the absolute worst-case scenario. If we can deal with this then maybe we are awarded with a self-esteem boost or something, so it's worth trying.

(At this point I stopped writing the blog post for 15 minutes to make more coffee and try to figure out what do to)


Okay I guess essentially, all we need to do is figure out the deepest workspace (or sub-workspace) that is common between the two blocks and then compare the lane indices there. In some cases, such as the simpler ones listed above, we will be able to detect the common workspace sooner and short-circuit out of this complicated algorithm, but in this nightmare case, I think the process is something like:

(A) has a macro level of 3, and (B) has a macro level of 2, so we start with (A). We search upwards through the macros looking for (B)'s workspace and never find it, ending up on the top-level workspace. Each step up the macro hierarchy, we make a note of the workspace's unique ID and pair it with the lane index we are on.

I'll give the workspaces IDs for clarity and let's assume there is the minimum number of lanes at each level:


So by the time we get to the top, we have some data which looks something like:

{ { workspace:#78, lane:1 }, { workspace:#56, lane:1 }, { workspace:#34, lane:1 }, { workspace:#12, lane:1 } }

We didn't find (B)'s workspace, so now we start searching upwards from (B)'s position. The first place we end up is at workspace#90, which is the sub-workspace of (B)'s parent macro. We check the data we collected for (A) to see if workspace:#90 appears. It doesn't, so we continue searching upwards.

Then we get to workspace:#34. Again we check if workspace#34 appears in the data we collected for (A), and it does. In the data entry for workspace:#34 we already made a note that (A) resides on lane 1 of the workspace, whereas in our current upwards search for (B) we ended up on lane 2. So we know (A) is above (B)!

Perhaps I missed something but this is the idea I'm going with for now.

And a quick v0.26.0 progress update: all i'm doing right now is fixing the remaining bugs that I know about (like this one). I only have a few bugs left on my list and most are not as complicated as this one as far as I know. So I'm in the final stretch now really.

Comments

No comments found for this post.