After a series of discussions on this forum several years ago, I decided to use Tracktion Engine for the sequencer app I’m developing. This got me off the ground, and my app is now working quite well (though it’s still under development). But as I’ve progressed, I’ve realized that Tracktion might not be the best fit for my app, and I’m now considering transitioning away from it. I’ve been thinking about how I might do this, and I’d like to present these thoughts here and gather some feedback from people with more experience than me. There are some quite specific questions below, so feel free to skip to that part.
Before this, let me briefly mention the reasons why I’m thinking of transitioning away from Tracktion.
The main reason is that my app is not a DAW, and so the workflow of Tracktion is somewhat at cross purposes. At this point there are really only three things that I’m using Tracktion for (listed below), and I’m wondering if I could improve my binary size and runtime efficiency by building my own tools for these jobs. On the other hand, as I’ve been warned in the past, these might be quite tricky to implement, and there’s always the “if it aint broke, don’t fix it” argument. So if anyone can convince me that any of the three services are more complicated than I realize and that I’d be foolish to try to roll my own, this would be very useful feedback.
The second reason I’m considering moving away from Tracktion is that I’m a bit put off by the lack of a perpetual license, and I don’t like the idea of having to pay monthly subscriptions if I ever decide to sell my software. This is in no way meant as a criticism (and it’s not as important as the first consideration) but I feel like my account here would be incomplete if I didn’t mention it.
So there are three things I’m currently using Tracktion for, and for each of them, my question is, “what would it take to roll my own?”
The first use is as a plugin manager. My app has two built-in plugins, and in the future I intend to expand this in the future so that the user can load their own plugins. Is it possible to accomplish this kind of thing in JUCE? I have a naïve sense that it shouldn’t be too difficult, but I’ve not really worked with this area of audio programming before, so I’m not sure.
The second use is for the Tracktion Sampler Plugin. In my app the user can load a bunch of samples and then trigger them using midi events. I’ve browsed through the Tracktion code here and it looks pretty complex—I’m not sure I’d want to write my own! But on the other hand this seems like a pretty generic tool, and I might be able to find some other open-source software for it, or even something within JUCE. Any ideas here?
The third use is the main one—I’m using Tracktion for its built in timeline. Without going into too much detail, my app isn’t really based around a timeline, and it has its own solutions for scheduling midi notes on the audio thread. I’ve worked hard on this and it’s now working pretty well; the only point of contact with Tracktion is with repeated calls to edit.getCurrentPlaybackContext()->getUnloopedPosition()
to find the current time. It’s this that I’m thinking of replacing. I’ve been warned repeatedly that this is harder than it sounds, but I might see a way of doing it, which I’ll outline below.
To get the current time, my plan was to get AudioPlayhead::PositionInfo
from within the processBlock()
function, and use getTimeInSeconds()
to set an atomic<double>
, which can then be called from another thread. The documentation for PositionInfo
says that some hosts may not provide time information though. So as backup, I could use highPrecisionTimer()
to constantly update the atomic<double>
. This would probably not be very accurate, but my thought is that without a host timeline to sync to, the inaccuracy probably won’t be very noticeable. To summarize, here’s a pseudocode implementation of what I’m thinking:
double getCurrentTime()
{
if (hostProvidesTimelineInfo())
return AudioPlayHead::PositionInfo::getTimeInSeconds();
else
return getTimeFromJucePrecisionTimer();
}
std::atomic<double> currentTime;
void processBlock()
{
currentTime = getCurrentTime();
// do other stuff
}
void functionFromSomeOtherThread()
{
double time = currentTime.load();
// ...
}
Does this seem viable, or is there something I’m overlooking here? If I’m being stupid here, please tell me, because it will save me a lot of time!