September 22, 2026

the daw had no ears

By Geoffrey Broomfield · 5 min read

I have a tool that can tell me a mix is bright and thin. It needs audio, and neither of my DAWs will give it any.

The question behind all of this is small. The same song reads bright and thin out of FL Studio and thick and dark out of Studio Pro, and both hosts sum in floating point, so the difference cannot be the summing. It has to be what is in the chain and in what order. That is a question about settings, and settings live in the project file.

FL’s API has no audio path at all. Studio Pro’s scripting API will answer almost anything about a session except what the session sounds like. So I stopped looking for a way to hear the mix and started reading the files.

what the file turned out to be

An FL project is a header and then a stream of events: one byte of id, and a payload whose size comes from the top two bits of that id. One of mine is 3,080 events consuming 998,576 bytes exactly. That exactness is the whole test. If the framing is wrong by a single byte, the count stops landing on the end of the data instead of the middle of a word.

The mixer is a 12-byte header and then 384 bytes per track, with one wrinkle: a 24-byte block sits between the tenth and eleventh records, so the first ten are packed tighter than a plain stride suggests. Volume is at +248 inside the record. A stored zero is not −24 dB, it is minus infinity — a muted track reads 0, and setting that same track to −12.0 dB moved it to 6699. The taper is not linear in dB either. A straight line fitted near 0 dB is out by more than a decibel and a half at the top of the fader.

Plugin order in the file is a lie in one specific way. Each plugin carries a 52-byte descriptor and the second dword of it is the slot. I moved one plugin’s block in front of another’s and FL still showed the original order, because FL reads the slot field and not the position. So a file can describe a chain that the arrangement of its own bytes contradicts.

what it found

FL’s mixer EQ has three fixed bands — a low shelf, a peak and a high shelf — and its gain control floors at −18.00 dB. A floored low shelf is not a notch around its corner. It removes everything below it. One vocal had nothing at all under 866 Hz.

Eight of those floors sat in one project, at corners with no relation to each other. Seven were low shelves. The eighth was the kick’s high shelf, which is a deliberate thing to want. I wrote a sweep to ask whether the pattern was a one-off or a habit, and it wrote a corrected copy of every project that had one.

what it got wrong

The first version of that sweep reported 41 of 62 projects as having the problem. Most were empty tracks with a fader somebody had dragged down and left there. A floored band on a track that is not faded up is not a finding, it is a closed channel. Adding the fader check is what turned 41 into a number worth acting on.

Two framing bugs, each of which cost real time. Only text events carry a length field, so giving every kind one desynchronises immediately. And the variable-length integer is the length — it is not "read a byte, then maybe read a varint if the high bit is set".

Text in an FL file is UTF-16LE, and decoding it as UTF-8 succeeds. Right characters, every second byte a NUL, which reads as noise rather than as an error. The detector is that every second byte is zero while the alternating ones are not.

One file’s mixer blob was 17,340 bytes and fitted no layout at all. The reader fell back to a plain stride, put every fader on the floor, and reported it. A wrong answer that looks like a finding is worse than a crash, because a finding gets believed. The layout is now found rather than assumed: an untouched track stores its three EQ corner frequencies as three int32s twelve bytes apart, so every position where that triple occurs is a record with its address attached.

And one claim of mine had to be withdrawn. I had written down that Studio Pro’s insert rack was unreachable. The six accessors I tested were names I had guessed from a community reference rather than enumerated from the host, and a null from a name I invented says nothing about what exists. The audit report I took it from also said inserts.numChildren = 3. A count of three children is proof the rack exists, and it was sitting in the same document as the word "unreadable".

status

Reading is solved. Writing is solved for the fields that matter. The catalogue has been swept and corrected.

Hearing is not solved, and cannot be solved from outside. Studio Pro addresses one audio device, and this machine’s interface exposes exactly one Loopback L/R pair. ASIO is exclusive, so nothing outside the DAW can attach to its stream. With no plugin in the path that is one stereo tap — the master, or whichever bus I route to it. I scanned all 212 installed plugin items and nothing on this machine streams audio. So per-track listening needs a plugin per track, and it has to be mine.

There was no compiler of any kind on this machine, so I fetched one: a portable zip, no installer, no administrator rights. The first attempt built a library that Windows refused with error 193, because the flag I passed made the compiler emit an archive instead of a DLL — and the build had exited zero, which is the part I keep thinking about. The second attempt worked. A 64-bit DLL that a Microsoft-built Python loads and calls.

The plugin is called Live Tap. Each instance publishes its output into a named shared-memory section — 32 slots, a 262,144-frame ring, 5.5 seconds at 48 kHz — with a frame counter that moves last, so a reader watching the counter cannot catch a half-written block. It sits inside the chain, which means it can also change the audio rather than only describe it. The bundle is built, and it exports its plugin factory and returns a real one.

It has not been loaded into the DAW yet.

I can read every fader, every EQ band and every plugin in forty projects, and the catalogue still sounds like nothing at all.

The last thing I built was not a reader.