Part 1. Let’s write a simple sine wave generator with C++ and JUCE
So, in this small tutorial I want to introduce how to create basic audio app and generate sine wave by formula. Firstly want to say a few words about JUCE. JUCE is the C++ library to develop cross-platform, interactive audio applications with graphics. JUCE supports Windows, Mac OS, Linux, iOS and Android. Also it gives you possibility to use predefined or custom UI. You can build VST, VST3, AU, RTAS and AAX format plugins with ease, and host VST and AU plugins inside your own applications. JUCE provides full MIDI support, new Multidimensional Polyphonic Expression protocol and Open Sound Control (OSC).
Installation and project creation steps
- Step 1. Download Visual Studio of you are on Windows (which version you want). I use Visual Studio 2015.
- Step 2. Download JUCE library from here. Extract all files into your working folder
- Step 3. Create simple audio app.
- Step 4. Open your project in Visual Studio, write code and build :)
Let’s create a project with Projucer
JUCE gives us great tool called Projucer. It can create project templates with basic code, configure your build solutions and manage your project configurations. Open the Projucer and click New Project. You should see a window as below.
Quick description what is Audio Application. Full description for all project types are listed here.
“Audio Application — This creates a blank JUCE application, like GUI Application, but automatically adds all the setup code that you need to easily get audio input and output. You can use this for games, multimedia applications, and much more.”
We need Audio Application project. Just write the name of you app, select appropriate target platforms and click Create… button.
After this window you should see you project window. You can add or remove modules, change your solution configuration and many more. For now just leave it as is. To open your newly created project in Visual Studio just press Open in IDE… button.
Now you can build project and run it. You should see empty window which does nothing, because we didn’t added any custom code, that’s OK :D
Let’s add custom code to generate simple sine wave
Great, we have a project template which successfully builds and runs on your machine. Now we want to add some code to generate simple sine wave.
We start with a simple sine wave formula:
What parameters can be customized?
- Amplitude (A)— we can use it as a volume
- Frequency (f)— how many cycles per second it does
- Phase (phi)— starting point of sine wave
- Time (t)— just a time counter value (in our case)
Okay, we have formula and need to translate it into the C++ code. Firstly we should create private variables in file MainComponent.cpp. Delta time is the smallest time value added to time variable, like a time step.
Next step is to initialize them in prepareToPlay(). This function is called once before audio stream starts to be processed. Here we set default values for our member variables. Yes, we can do this in the constructor, but currently it is not so important. Sample rate stands for samples per second. For example, most commonly used is 44100 Hz. In our case we need to know how much time passed from one sample to another. This time is constant and remains stable, but depends on sample rate. So if our sample rate is 44100 Hz, it says that we should take 44100 samples per one second. If we divide 1 over 44100 we get delta time between samples. This is the delta time value.
Next part will be the most interesting for us :D. What’s going on in that code below? Firstly we need to get how many output channels we have and iterate over them (standard is to 2 channels, stereo signal). Also it should be noted, that we need somehow to modify our output buffer with sine wave data, so we get a pointer to that buffer by calling getWritePointer() for each channel. Buffer can have variable length, it can be modified by user or system preferences. And in the last loop we just increase our time variable by delta time and use sine wave formula to generate values, which be assigned to buffer. Take note that time variable is limited by it’s max value to prevent overflows. And now you can run this application and hear a sine wave at 500Hz frequency :)
Adding basic GUI to control parameters
Include Slider and Label classes in MainComponent.cpp file and create class members in private section. After that we need to initialize this GUI elements in the class constructor. And by the way fix windows size to smaller one. Please take note that we should also derive our class from Slider::Listener to handle slider changes and TextButton::Listener to handle mute button clicks. All slider changes by user will call sliderValueChanged(). When user clicks on a mute button — buttonClicked() method is called. Also we should write code in resized() method to update widget positions. Volume slider uses logarithmic scale. New parts with GUI code are listed below. Take note that this code should be intelligently merged to your existing, not replaced.
Change volume slider from linear to logarithmic scale (dB)
Decibel is a logarithmic unit used to express the ratio of two values of a physical quantity, often power or intensity. In acoustics it is used to measure sound level, or more popular — sound volume. Here you can find comparison chart for different sound levels. In computer music values should not go above 0.0 because of clipping, so we’ll have different range:
- -96 dB — Mute
- 0 dB — Max volume
- > 0 dB — Additional amplification (can produce clipping)
To convert between linear and logarithmic scale use this formulas:
Firstly I changed the range for volume slider in the class constructor. Also pay attention how I modified sliderValueChanged() method to convert volume slider value from logarithmic scale to linear.
Removing artifacts when changing frequency
If you change frequency, you can hear audible artifacts and glitches, which are needed to be removed. They are created by rapidly changing frequency values. Therefore we need to smooth frequency change. I won’t be implementing this feature now. The main idea is to smoothly interpolate between current frequency and target frequency in some time period.
That’s all folks :D
And that’s all, just run the app and listen your sine wave :)
Source code is stored on GitHub.