In this assignment you are asked to create a spectrum analyzer, but now using Faust instead of the juce FFT library. Through this assignment you will learn how to:
As usual, feel free to go wild! However, your implementation must have the following components:
This project will build off of Assignment 1. Your GUI from the previous assignment can be reused. Just make sure to remove all references to the JUCE FFT library. You will now be generating your data via Faust.
To analyze our input signal we will use the Faust library's mth_octave_analyzer
functions from their analyzers.lib
library.
The Mth-Octave Analyzer implements a filterbank which splits the signal into a parallel bank of signals, one for each spectral band. For our purposes, we will use the default analyzer which in turn uses bandpass filters based off of 6th-order elliptic filters:
mth_octave_analyzer_default = mth_octave_analyzer6e;
mth_octave_analyzer_default
has three parameters:
M
number of band-slices per octave (>1) ftop
upper bandlimit of the Mth-octave bands (< SR/2)N
total number of bands = number of output signalsM
equal to 1 we will
produce N
octave
bands, starting with a highpass band crossing over to the next band below at frequency
ftop
, and band-splitting downward from there by octaves.
To see the block diagram of this filter bank, say (at the CLI)
faust2firefox ~/faust/examples/filtering/filterBank.dsp
and click down three levels to see the octave filter-bank architecture.
For a simpler example, consider:
process = an.mth_octave_analyzer_default(1, 10000, 2);
which results in the following block-diagram:
This process splits the spectrum into two bands having a crossover frequency at ftop
= 10 kHz.
In this case, only a lowpass and highpass are used (no bandpass sections). If we increase N
to 3, then the lowpass band gets
split into a new lowpass cutting off at 5 kHz, introducing a
bandpass from 5 to 10 kHz, and so forth
The following Faust program can be adapted to check your filter bank in Python, Octave, or MATLAB, etc.:
import("stdfaust.lib");
N = 3;
fTop = 10000;
bandsPerOctave = 1;
displayOffsetDB = 5;
process = 1-1' : an.mth_octave_analyzer_default(bandsPerOctave, fTop, N)
: par(k,N,*(pow(10.0,(k*displayOffsetDB)/20.0)));
This Faust program, as written, outputs three signals containing the band impulse-responses.
The parameter M
will change the size of the octave band being used. This can be used to implement fractional octave smoothing. If we wanted 1/3-octave smoothing we would then set M
to 3. With M
equal to 3 and N
both equal to 5 we produce three 1/3rd octave bands between 10 kHz and 20 kHz with two additional highpass and lowpass bands above 20 kHz and below 10 kHz.
Try implementing an.mth_octave_analyzer_default(3, 10000, 14)
and listening to the different channels!
In our previous assignment we used a leaky integrator to smooth the output of FFT. Here we will do the same using a combination of the si.smooth()
and ba.tau2pole()
functions.
si.smooth
is in Faust's signals library and implements a dc-unity gain one-pole lowpass filter, which is essentially our leaky integrator. We need to provide the si.smooth()
function with our pole location. To do this we call ba.tau2pole()
and provide our time constant to the function. Since we want to smooth N
bands we use the par
integrator
par(i, N,si.smooth(ba.tau2pole(tau))
Since we want to take the power of our signal before smoothing we'll use the par
iterator and an absolute value before we smooth our functions par(i, N, abs(_)^2)
.
We want to control our \(\tau\) parameter like before. Add an hslider
to your Faust code.
tau = hslider("smoothTime", 0, 0, 10, 0.1)*0.001;
Implement a your filterbank in Faust and compile it to C++ and add it into your JUCE project as in Assignment 1.5.
It could be helpful to delete the old FFT size macros and replace them with macros that define the size of our Faust filterbank
#define N 12 // yourNumberOfBands
#define M 3 // yourBandSlices
We also need to create new float arrays to use in our visualizer plot:
float fbData[N];
float frequencies[N];
Attach fbData
to your Visualizer
in your plugin constructor. In prepareToPlay
calculate the center frequencies of your frequency bands. Hint: look at the Faust implementation of an.mth_octave_analyzer
To store the output of Faust filterbank we will declare a juce::AudioBuffer<float>
private member. Based on our Faust code our audio buffer member should have N
channels (the number filterbank bands) and match the number of samples of our input buffer.
So we will declare
juce::AudioBuffer<float> fbBuffer
in our header file. And in prepareToPlay
we will set our buffer size
fbBuffer.setSize(nBands, samplesPerBlock)
Now we can allocate a double pointer, say float **faustOutputPtr
in our header, and in prepareToPlay
attach your Faust audio buffer to this pointer
faustOutputPtr = fbBuffer.getArrayOfWritePointers();
Use this buffer and pointer combination to call our compute
function on the first channel of our input in processBlock
fDSP->compute(buffer.getNumSamples(), buffer.getArrayOfWritePointers(), faustOutputPtr);
After calling compute
we need to write our output to our filterbank plot
for (int band = 0; band < N; band++)
{
for (int samp = 0; samp < buffer.getNumSamples(), samp++)
{
fbData[bin] = faustOutputPtrs[bin][samp];
plot->update();
}
}
Now we can update our filterbank plot at every sample! Trying adding in the other parameters your used in your previous assignment to your Faust code and hooking them up in JUCE!