devlog > daw
Improving DAW compatibility
Captain's Log: Stardate 78294.1
DAW Licenses for Testing
After releasing the pre-alpha version 0.0.8, I got feedback that Anukari was crashing as a plugin in Bitwig. This reminded me that I hadn't done a detailed check on DAW compatibility/stability in a while, so I sat down to do that.
The last time I did this was in the first week after starting the pre-alpha, and I did it by installing the free trials for a bunch of DAWs. Unfortunately, in the intervening 6-8 weeks, all of the free trials had expired. So, for example, I was hosed in terms of debugging the Bitwig crash locally.
So I decided to email the top 10 or so manufacturers for DAWs that I'd like to test against, and asked them if they would give me a free copy of their DAW for this purpose. I'm not sure exactly what I expected to happen here, but I definitely didn't expect for them to universally say "yes," which is what happened. So far, every manufacturer that's responded has not only given me a license key, but given me a key for their top-of-the-line product with all the extras. For example, Reason Studios not only gave me Reason 13, but also unlocked their entire line of plugins. Which is incredibly generous, and also unnecessary. But I won't complain.
It turns out this is a normal thing in this kind of industry, and there's even a special kind of license for it: "Not for resale" (NFR). Which makes sense; given the purpose of the license it would be ridiculous to go off and resell it.
Anyway, this is great news, as I now have 13 of the most popular DAWs installed on my Windows development machine, and after some work, have gotten Anukari working in all of them with no crashes. I haven't installed them on MacOS yet but I'll do that soon as well.
The Most Fun Compatibility Bug
The crash in Bitwig was somewhat interesting. What was happening is that Bitwig actually initializes the DAW GUI before it has created a native window for it to draw in, and my code was assuming that a native window would be available at that time. Fixing this just required using JUCE's parentHierarchyChanged() method to watch for changes until the native window became available.
The more fun bug was the crash in Cakewalk Sonar. Pretty quickly I figured out that the crash was happening when Anukari called SymInitialize(), which is a Windows API function that you can use to include symbols in crash logs, which makes them vastly more usable. This function has the user-friendly property that if you call it twice in a given process, it crashes on the second call. It's a bit surprising to me that Cakewalk is the first DAW I've run into that calls this function, since it's quite useful, but it is.
The obvious thing to do is for Anukari to somehow check whether SymInitialize() has been called and don't call it. But I read through the Windows docs and didn't find any way to do this, and Mr. ChatGPT stated very confidently that it was not possible. Still, I wasn't convinced and kept Googling, and eventually found this tweet from my programming hero Jonathan Blow (the creator of the amazing video games The Witness and Braid). His initial tweet summed up my frustration over the impossibility of the situation, but someone eventually replied with a solution, which is to call SymEnumProcesses ().
Now, this is a damn sight better than blindly calling SymInitialize(), which guarantees a crash if the DAW already called it (like Cakewalk Sonar). But... it's still technically impossible to do it safely. This is because calling SymEnumProcesses() concurrently from two threads is not safe, and of course Anukari has no way to know whether any other thread running in the DAW might call it. Still I went with this solution, because it is unlikely that anyone else is calling this function, and if they are, it's unlikely that it is concurrent with Anukari, and even if so, Anukari is calling it in a read-only kind of way, so maybe it wouldn't crash anyway. Hopefully. :)
On-screen tuner
Captain's Log: Stardate 77850.6
Today I finished adding an on-screen tuner. This is something that I've wanted for a long time, but always put off because it was easy enough to use e.g. the Ableton tuner or even my guitar tuner app on my phone, etc. But both of these solutions are somewhat annoying in different ways.
I really did not want to become an expert on pitch estimation algorithms, so I researched a bunch of libraries that provide pitch estimation and systematically went through and evaluated them. One annoying factor was that over half of the libraries out there, especially the ones written by academics, used the GPL3 license, which means they're a no-go for a commercial project such as mine. (This is fairly annoying because I'd guess many of academics didn't realize they were hugely restricting who could use their sample code.)
That said, most of the academic pitch estimation code is total shit, so it wasn't the biggest loss. I'm sure the algorithms are amazing, but calling malloc() 8 times per sample buffer when it could very easily be avoided is just stupid.
In the remaining group of actually free -licensed code (MIT / BSD mostly), a lot of it was also total shit. There are a bunch of projects that are evidently hobbyists who had fun implementing pitch estimation papers. Which is fun, but also not useful.
Some of the projects that looked sort-of OK just didn't work. The best MIT-licensed pYIN estimator I found was unable to detect frequencies above about 2.7 kHz (and that was after a bunch of tweaking).
Anyway, I finally settled on https://github.com/cycfi/q. It's well-implemented, easy to use, has a good cmake configuration, and best of all, their pitch estimator works extremely well. It's super fast and stable.
Here's what the tuner looks like.
Golden tests for DAW automation
Captain's Log: Stardate 77814.9
DAW/host parameter support is finished, including golden tests, etc. Everything works how I'd like it to.
The golden tests were slightly interesting. Since, obviously, a MIDI file can't contain host automation, it wasn't immediately obvious how to write golden tests for this. My first thought was just to hard-code a few automation events and do the tests that way. But what I came up with was a little config parameter that lets the golden tests request that specified MIDI continuous controller #s be translated to host automation inputs.
So, in Ableton, I created the test preset and set up some automations in two lanes. Once I got this working how I wanted, I copied and pasted the automation lanes into MIDI CC 1 and 2, and exported the MIDI file. Then in the golden tests, I mapped CC 1 to automation lane 0 and CC 2 to automation lane 1. The golden test produced exactly the output it was supposed to. This gives me a lot of flexibility in how to test this kind of thing.
Aside from finishing the host automation stuff, I tracked down and fixed a bug where undo/redo worked, but the right-hand side panel didn't update to reflect the changes from undo/redo unless you cleared the selection and re-selected the entities. This ended up being a nasty bug in the subscriptions code, which caused (a) some subscribers to see irrelevant changes, and (b) some subscribes to miss changes intended for them. Strictly speaking, #a was a problem with the subscriptions code, and #b was a misuse of the subscription API. But even the API misuse was because the API was bad. Both of these are fixed, and tested.