Noise Gate Part 4
If you’re just joining us or if you futzed your project due to my harebrained instruction, here’s the file from where you should be at. You should (if you’ve followed the blog series so far) understand every line of code, where it comes from in the documentation and what it’s supposed to be doing.
We’re getting to the bit I dread. The actual Audio programming part.
How we know these particular functions need to be written is another story that’s developing as we go! Sit tight, grab a croissant and stare lovingly across the room at that coffee shop barista as we get into the nitty gritty stuff of prepareToPlay.
If we move to our PluginProcessor.cpp and find the
void NoiseGateAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
Then hold ⌘ + Click on the function, it’ll open a small dropdown box that let’s us select which definition we’d like to take a look at. For our purposes right now, we’re going for the source, so we can find the actual meaning of prepareToPlay. Jump to the definition in the audioProcessor and we’ll find the function a little ways down (or your selection should have taken you straight to it.
There’s a wee bit of terminology here that’s important, but we’ll start by talking about virtual functions. prepareToPlay is a virtual function, which means that we are supposed to redeclare it, which will then mean it will be overriden by the new information. This has to do with memory allocation and something known as polymorphism, but let’s steer clear from that for right now. There are a few virtual functions in AudioProcessor, let’s see if we can find them, and then check our preset code to see if they show their ugly mugs.
Virtual Functions in AudioProcessor
So we have;
When you create a new class that's a child of an existing class, it inherits all of the public methods from its parent. This leads to two common situations:
The child must override one of its parent's methods in order to gain new functionality. C++ makes this easy by allowing you to automatically override the original method by giving the new method the same name, inputs, and outputs.
Your program needs to work out whether it's the parent or a child, and C++ makes this easy by allowing you to return pointers to any type of class (parent or child) and cast them as pointers to the parent class. (more on this later)
If both of these things happen at the same time, it leads to an ambiguity: if a particular method is called, should it be the method of the parent class (based on the pointer type) or the method of the child class (based on the actual object)? C++ defaults to the parent's method unless you declare the parent's method as virtual, e.g.:
virtual void parentMethod();
With virtual functions, there's no more ambiguity. The program will definitely use the child's method in the case of virtual methods, and will definitely use the parent's method in the case of non-virtual methods. The GUI uses virtual methods extensively, for example in the case of the GenericProcessor class, which contains numerous methods that must be overridden by its child classes.
So JUCE is building much of the functionality behind the play, so that we can inherit much of the same functionality without having to rebuild it all the time (basically)
WHY DO I CARE?
Because JUCE (and C++) has one more important feature. Pure virtual functions (untainted by assholes). So, when we run into a Pure Virtual Function, we HAVE to declare it, so that the class is happy. If we fail to declare them, the class will get very unhappy. Let’s break some hearts.
In the documentation, we’ll find pure virtual functions in the right hand column. It looks lyk dis;
If we look here and scroll down to our prepareToPlay method, we’ll find that it is indeed a pure virtual function. Which means we can make things very cross by removing it…but we wouldn’t do that…
If you’re still in the class declaration for prepareToPlay and you’re lost and confused, fear not. In the file hierarchy in the top bar, click the left arrow to go back to where you came from.
Now that we are back in the PluginProcessor.cpp, comment out the lines that have the entire prepareToPlay method. In C++, we don’t usually want to just delete what we’re writing, and so often what we would like to do is simply remove it for a time. On top of that, if we’d like to tell other programmers about the dumb shit we’re doing in our codebase, we want to be able to talk to them, rather than have to print strings (words) to the terminal.
Use the //
Simply comment out the crap you’d like to hide on each line with two slashes like so //
Or, if you’d like to remove an entire block of code, we can use the /* at the start of what we want to hide, and */ when we are done, like so.
Which you choose is up to you. Once it’s all green and commented out (or if you’re in dark mode..well, it’s still green. I’m not a big fan of dark mode you see, it doesn’t gel with my colour scheme). Anyway, once you’ve commented it out, build the project again, which should fail.
Ugly error right? This is one of those that gets very scary, very fast. But I’ll hold your hand and we’ll make it through it. Hopefully this demonstrates why we have to pay attention when writing out our own functions and keeping up with pure virtual functions. In our case, the Projucer has actually formed some of this for us, so we couldn’t have missed them, but once we’re finished this tutorial and we start writing more, we’ll start to realise that if we just check out the documentation, we’ll work it out.
For instance, let’s find another pure virtual function, and delete that, and see what happens. Scrolling up I find that getName() is also a pure virtual. Let’s find it, and break it. Here it is, a little further up in the documentation. Notice that I skipped over getProgramName, although that’s a virtual function too!
Either one that we comment out totally kills Xcode, and we’ll be left head scratching.
Moving on from pure virtual..
Now that we’ve discussed this, let’s dissect what’s going on with prepareToPlay
So the prepare to play function is getting ready to start, and we’re initializing two variables, lowPassCoeff and sampleCountDown
The low-pass coefficient will be calculated from the sidechain signal and the alpha parameter to determine the gating behaviour. It’s important to remember that we’re not actually doing any audio filtering, but just data filtering. The terms are quite common in scientific circles. So when we’re thinking about using a low-pass coefficient, we don’t mean one of these fat boiz.
We’re just talking about allowing only values lower than the threshold point. This means that once the sidechain input hits the threshold, we’ll be getting NO audio. Not a lower amount (it’s not a compressor), and not a filtered thing (thought that would be swell). NADA. That would require a WHOLE LOT of other programming and things like IIR and FIR filters. That’ll probably be the next one I tackle. Maybe a compressor. Not sure. Please let me know your thoughts and that’ll make the decision for me!
The other variable, sample countdown allows us to keep track of the number of samples left with regards to the sample rate before the gating occurs. I guess this is to stop clicks and pops, though we’ll have to keep looking further into the code to work it out.
Looking a little further. The tutorial itself actually lays it on us that;
Because of course they are, and I haven’t yet worked out why they couldn’t show us one of them. But I guess they’ve got to keep that? Or do they? I really don’t know at this point, but we’ll roll with it.
There are a few things I’d like to expand upon in this project already. As we get to the end, we’ll look at adding in some GUI to allow us to change the threshold, as once we have it working, it’ll be pretty ON or OFF sort of working until then.