Dev log #28 - Song traversal (warning very boring) (Patreon)
Content
There's a pretty bad bug in Blockhead at the moment where if you go far enough left or right in the workspace and start playing audio all the way over there then you will start to hear a sample reduction effect, which gets worse the further to the left or right you go. This is the most audibly noticeable bug in a family of related bugs that I am currently working on related to floating-point precision errors.
The engine processes audio in buffers of 64 frames. At the start of each buffer it generates 64 song positions representing the playback traversal. For example if you just open Blockhead and press the spacebar the first thing it will do is generate the numbers 0..63, then on the next buffer it generates 64..127, etc. If the playback hits the end of a loop region then the numbers might jump back mid-buffer, e.g. [567, 568, 569, 123, 124, 125, ...] etc.
This is only actually the case if the audio device is currently set to 44100 Hz, because then it matches Blockhead's internal "song rate" which is hard-coded to 44100. If the song rate and the sample rate don't match then the engine increments the song position differently. For example if the sample rate is 88200 Hz (exactly double the song rate) then the engine will generate [0.0, 0.5, 1.0, 1.5, 2.0, ...] etc. The sample rate can theoretically be anything so the numbers have to be passed around in a format that can represent fractional numbers.
This internal song rate thing is a problem unique to Blockhead's design because by default I want the workspace to act like a big canvas that you can freely drag things around on without having everything lock to a global grid. Constraining elements to a grid is an optional thing that you can add where you want it.
The song rate of 44100 is essentially the resolution of the workspace (there are 44100 valid positions that the edge of a block can be at, over a 1-second range of time). I just picked 44100 because it's a common sample rate and it's nice when then song rate and sample rate match but it could be anything. The engine still needs to process audio at the sample rate regardless of the song rate.
As far as I know this is a completely different approach to most other DAWs which just have a global project tempo and an internal MIDI clock which they can snap everything to, which simplifies everything. Sometimes you can opt to turn off the grid and drag things around freely but I think this is usually achieved by still having things still technically locked to the grid but then having an extra "offset" value which nudges the element backwards or forward in time.
The problem in Blockhead right now is when these fractional numbers get big enough they start to lose precision, which often manifests audibly as a kind of sample-rate reduction effect.
There's a few solutions to this I have thought of. Using double-precision floats to represent positions has various problems and would be a pretty hacky solution anyway.
Currently I'm reworking everything so that instead of using a single fractional number, each song position is represented by an integral part and a fractional part. So some massive song position like 266592848.736 would be split into two pieces: 266592848 and 0.736.
This way the size of the song should only be limited by what it's possible to represent using a 32-bit integer. At a song rate of 44100 this puts the limit at roughly 27 hours in either direction of the zero point (since Blockhead lets you put things to the left of the zero point too).