NokiMo
colugomusic
colugomusic

patreon


Dev Log #34 - Block Transitions

The Edges

To avoid clicking at the right edge of a block, Blockhead automatically adds a short fadeout to the end:

(I am drawing these lines freehand in MS Paint so they won't be accurate.)

This fade-out is very short but enough to eliminate nasty sounding clicks that can occur when a sample suddenly stops playing back.

During regular playback, no fade-in is added at the left edge as it's assumed that more often than not it's desirable to playback the data at the start of the sample as-is.

By hovering over a sample block and pressing S you can slice the block in two:

Now the left-hand block's fadeout period overlaps the right-hand block:

There is a design constraint I decided to adhere to here which is that if a slice is performed and the two resulting blocks remain where they are, with their edges touching, and both blocks have identical settings (amp, pitch etc.), then when the song plays back it should sound as if the slice is not there. There should be no momentary dip or flutter in volume as the left-hand block fades out and the right-hand block begins.

When a slice is performed the block on the right stores an offset indicating how much to shift over all the data (including envelope data). If a slice is created at position 12345 in the block then the block on the right stores a data offset of 12345. The data offset for the block on the left stays at zero.

When the playback hits a block and the data offset is more than zero then a fade-in period is generated:

When the blocks are touching the fade-out of the left-hand block and the fade-in of the right-hand block should match exactly so this gives the desired effect of having the slice not be audible.

If the two blocks are pulled apart they still fade-out and fade-in in the same way and there will be a dip in volume where the gap is.

The user can manually adjust the data offset of a sample block by shift-dragging the left edge. If it's reset back to zero then the fade-in period is removed:

The fade-in and fade-out periods have a maximum length but they can never extend beyond the right-hand edge of the next block on the lane like this:

In this situation the fade-out period is automatically shortened:

Therefore blocks always need to be aware of the blocks they are adjacent to.

Loops

When a loop point is hit another layer of crossfading occurs on top of this system:

When the playback hits the right-hand edge of the loop block and jumps backwards, for a short period of time the engine continues processing and fading out the song from where it jumped from, while also processing and fading in the song at the loop start.

(This system also has to interact with another de-clicking system which kicks in whenever a block is dragged around or modified while the song is playing back. That system is extremely complicated and I won't discuss it in detail but briefly: When the user updates a block in some way the engine keeps the old version in memory for a moment, crossfades it out and crossfades in the new version. This eliminates most clicks and glitches caused by making changes to a block while it is playing back. If this happens to occur at the same time as a loop transition which interacts with the block then, for a moment, there will be four versions of the block playing back at the same time!)

In v0.12.1 there is a bug where if the song loops back to the start of a sample block then the start of the sample is softened as the loop section fades in:

Since I have been rewriting a large part of the engine to support the new effects system I took the opportunity to fix this. In the new system the engine will detect if the block was triggered at the start, and the data offset is zero. If both are true then the transient at the start of the sample will now be preserved:

This means that the loop transition cannot be processed on top of everything else at lane-level or track-level. It has to be processed individually for each block. Consider the following situation:

When the song loops back to the start of the loop region, the block on the top lane is being triggered in the middle so it has to fade-in (to avoid a click), whereas the block on the middle lane should start at full volume (to preserve the sample transient).

There are a lot of awkward corner cases to deal with, for example:

Here the sample block on the right is triggered half-way through the fade-out period of the loop transition. It is still being triggered at the start so the transient should be preserved, but it should still be affected by the fade-out.

Effect Blocks

Part of the reason I decided to start concentrating on this stuff is the introduction of the new effect blocks (which I hope to actually start working on soon).

In the new effects system you'll be able to apply an effect to a sound by placing it underneath in the same track:

Unlike other DAWs, effects will exist momentarily for the range that the block covers. They process whatever audio is coming from the lane above. (By default, sample blocks will mix with the audio coming from above, so sounds on multiple lanes can be routed into the same effect on the lane below.)

Clearly care needs to be taken to crossfade the edges of the effect blocks too, to avoid the clicking when the effect suddenly starts and stops. It seems obvious that a naive fade-in at the start of an effect block won't work well:

With this approach some dry signal would be let through at the start of the block. (The user could manually drag the left edge of the effect block over so that it starts before the sample block, but that's counter-intuitive.)

Instead the effect begins processing before the left-hand edge of the block so that it always gets to 100% before it hits the edge:

It is tempting to say that effect blocks should always remain at 100% wetness until they hit the right-hand edge, but this doesn't work when you consider what happens with adjacent effect blocks:

In this situation both effect blocks are processing the dry signal being routed down from the lane above. At the point where the effect blocks touch, both effects are at 100% wetness (200% total) when really we would like them to crossfade perfectly into one another so that the total wetness never exceeds 100%.

If the effect blocks are touching, then the total effect wetness should remain at 100% at all times, with no dry signal coming through, resulting in a clean "one effect into another" transition.

I have developed an algorithm for correctly dealing with the transitions between effect blocks which you can play with here:

https://www.desmos.com/calculator/klzocaliij 

The red line is the calculated amount of dry signal to let through. This will always be the inverse of the total amount of calculated wet signal between the two effect blocks. The crossfade region is automatically adjusted so that it never exceeds past the far edge of the adjacent block.

Finally, since audio blocks (e.g. sample blocks) crossfade in a different way, and effect blocks can be placed on the same lane, I developed two more variations of this algorithm to deal with crossfading between the different types of block (one for [audio -> effect] and another for [effect -> audio].


Related Creators