Cmajor is Awesome

8/9/2023

Recently, I've started learning about audio programming for a side project (more on this in the next post!). I started by trying to learn JUCE, but I found it awkward to work with, especially since C++ isn't my strongest language. It introduced a lot of boilerplate just for simple audio processing, and it wasn't very intuitive. And I knew that this was the easy part - UI would be even harder. However, it seemed there wasn't any alternative, so I just tried to stick with it. That's when I joined "The Audio Programmer"'s discord server, and an odd channel caught my attention - Cmajor it was named. I searched around a bit and eventually found cmajor.dev, the official Cmajor website. So, what's this all about?

A language for audio processing

Cmajor is a domain specific language, that is, a language made specifically for audio processing. It can't really do anything else, but it can do audio really well. It has a simple syntax based on processors and graphs, which are exactly what you'd expect - processors process a signal, and graphs link processors together, in a way similar to block diagrams. All of this is extremely intuitive, and maps almost directly (at least for me) to the mental model of the effect. Further on, it's a C-style language, so anyone who's done some programming can pick it up really quickly. Don't believe me? Then see it for yourself. Here's a simple Cmajor program, and I'm pretty sure you'll be able to understand it (as of now, there isn't syntax highlighting for Cmajor, but I'll try to fix that soon):

1processor SineOscilattor [[ main ]]
2{
3 output stream float out;
4
5 input value float frequency [[ name: "Frequency", min: 20.f, max: 20000.f, init: 440.f, unit: "Hz"]];
6 input value float amplitude [[ name: "Amplitude", min: 0.f, max: 1.f, init: 0.2f]];
7
8 float phase = 0;
9
10
11 void main(){ //
12 loop{
13 out <- amplitude * sin(phase);
14 float deltaPhase = float(frequency * processor.period * twoPi);
15 phase = addModulo2Pi(phase, deltaPhase);
16 advance();
17 }
18 }
19}

And here's how it looks (I'm loading it with the vscode plugin):

This GUI is generated by Cmajor based on the annotations provided in the program

Performance by default

Cmajor is extremely efficient. Even without doing any fancy stuff, which we'll discuss later, it can match and often beat native C++ performance - without any effort! All this is achieved by a JIT compiler, which squeezes every little bit of performance it can from the target machine. But when coding, you don't need to even know this. You just code, and Cmajor does its magic to optimize your code. But that's not all it can do. Let's take a quick detour to graphics land.

How graphics programming works

Graphics programming usually involves a lot of computation-heavy tasks. Doing this on the CPU would be very inefficient, so engineers created a specialised device for graphics: the GPU. When targeting a GPU, you code shaders, which are basically programs that run on the graphics card. By doing this, you can benefit of massive parallelization, which is ideal for graphics programming. These shaders are written in either GLSL or HLSL, both being languages made specifically for GPUs. These languages are usually translated to a sort of "GPU assembly" by the drivers, and the resulting instructions are executed on the graphics card. The two main benefits of this approach are that it frees up the CPU to do other things and it offloads specialised work to a specialised worker.

So, why isn't this technique applied to other specialised tasks - like audio processing?

Cmajor as an "audio shader language"

Cmajor was created with the intent of being run on specialised units when they're available. You might be surprised to learn that most devices have these specialised units. They're called DSPs and are made for audio processing. However, unlike GPUs, most drivers don't allow programmers to run their own code on them, meaning they're hardly used. Cmajor's creators want to change this, and offset audio processing from the CPU to the DSP. Of course, this will require driver support, which will take a long time.

Disclaimer: technically speaking, this is speculation. What I describe here were the known goals of the creators of SOUL. It just so happens that the creators of SOUL are the creators of Cmajor, and the two languages look almost exactly the same. So, I think it's safe to say that the goals of SOUL and of Cmajor are also pretty similar. But don't take my word for it - there's no promise from the Cmajor team that this will ever be a reality. (On a side note, I couldn't really find out why SOUL was abandoned and then Cmajor was created, but if you know anything about this, I'd be really interested in hearing, so share your thoughts in the comments!)

Easy UI

Another of Cmajor's strengths is the way it implements custom UIs. Instead of fiddling with hard C++ UI implementations, you create UIs with a language that was actually made for UIs - good old HTML and CSS. Of course, this means you can also use any frontend framework you like, like React, Angular, Vue or Svelte. This makes it super easy to create elegant, polished and functional UIs for your plugins.

Conclusion

I sure think that Cmajor is pretty amazing, and I hope by now you agree with me. However, it doesn't get nearly as much attention as it deserves, which is a shame. Anyhow, let me know what you think in the comments, I'd really like to see some other points of view!

Food for thought

If you want to know more about Cmajor, I recommend you watch these two ADC talks:

I've also been reading a book about DSP, so if you're into that kind of stuff, here it is: INTRODUCTION TO DIGITAL FILTERS (it's very math heavy!)

That's all I have to say for now. Next time I'll be talking about a new project, which is the reason I've started learning audio programming in the first place, so keep your eyes peeled!

Previous Post