Implemented command line argument parser
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
../Bela/scripts/build_project.sh ./src/ -p Assignment_1
|
||||
../Bela/scripts/build_project.sh ./src/ -p Assignment_1
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
../Bela/scripts/run_project.sh Assignment_1 -c "-f 1000"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
../Bela/scripts/run_project.sh Assignment_1 -c "-f 3000"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
../Bela/scripts/run_project.sh Assignment_1 -c "-f 1000 --linkwitzriley"
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
../Bela/scripts/run_project.sh Assignment_1 -c "-f 3000 --linkwitzriley"
|
||||
+85
-32
@@ -5,7 +5,9 @@
|
||||
|
||||
class Filter {
|
||||
public:
|
||||
Filter(float crossoverFrequency, float fs, bool highpass=false, bool linkwitzRiley=false) {
|
||||
Filter(float crossoverFrequency, float fs, bool highpass=false, bool linkwitzRiley=true) {
|
||||
// Filter class constructor is used for the calculation of filter
|
||||
// coefficients and delay line memory allocation.
|
||||
|
||||
// Calculate ratio between cutoff frequency and sampling rate
|
||||
double wc = crossoverFrequency/fs;
|
||||
@@ -16,64 +18,104 @@ class Filter {
|
||||
// Warp the frequency to convert from continuous to discrete time cutoff
|
||||
double wd1 = 1.0 / tan(M_PI*wc);
|
||||
|
||||
// Calculate coefficients from equation
|
||||
// Calculate coefficients from equation and store in a vector
|
||||
numerator.push_back(1.0 / (1.0 + q*wd1 + pow(wd1, 2)));
|
||||
numerator.push_back(2 * numerator[0]);
|
||||
numerator.push_back(numerator[0]);
|
||||
denominator.push_back(1.0);
|
||||
denominator.push_back(-2.0 * (pow(wd1, 2) - 1.0) * numerator[0]);
|
||||
denominator.push_back((1.0 - q * wd1 + pow(wd1, 2)) * numerator[0]);
|
||||
// If the filter is a high pass filter, convert numerator
|
||||
// coefficients to reflect this
|
||||
if(highpass) {
|
||||
numerator[0] = numerator[0] * pow(wd1, 2);
|
||||
numerator[1] = -numerator[1] * pow(wd1, 2);
|
||||
numerator[2] = numerator[2] * pow(wd1, 2);
|
||||
}
|
||||
// If the filter is using the Linkwitz-Riley filter structure,
|
||||
// convolve the numerator and denominator generated for the 2nd
|
||||
// order butterworth filter with themselves. This creates the 5
|
||||
// coefficients of 2 cascaded 2nd order butterworth filters needed
|
||||
// for this filter structure.
|
||||
if(linkwitzRiley) {
|
||||
//rt_printf("Num size: %d\n", numerator.size());
|
||||
//rt_printf("Den size: %d\n", denominator.size());
|
||||
numerator = convolve(numerator, numerator);
|
||||
denominator = convolve(denominator, denominator);
|
||||
rt_printf("Num size: %d\n", numerator.size());
|
||||
rt_printf("Den size: %d\n", denominator.size());
|
||||
rt_printf("%f %f %f %f %f\n", numerator[0], numerator[1], numerator[2], numerator[3], numerator[4]);
|
||||
rt_printf("%f %f %f %f %f\n", denominator[0], denominator[1], denominator[2], denominator[3], denominator[4]);
|
||||
}
|
||||
else {
|
||||
rt_printf("Numerator: %f %f %f\nDenominator: %f %f %f\nHighpass: %s\n",
|
||||
numerator[0],
|
||||
numerator[1],
|
||||
numerator[2],
|
||||
denominator[0],
|
||||
denominator[1],
|
||||
denominator[2],
|
||||
highpass ? "true":"false");
|
||||
//
|
||||
// Print coefficients to the console
|
||||
rt_printf("\nNumerator:\t\t%f %f %f %f %f\nDenominator:\t\t%f %f %f %f %f\nCrossover Frequency:\t%f\nHighpass:\t\t%s\nFilter Type:\t\t%s\n\n",
|
||||
numerator[0],
|
||||
numerator[1],
|
||||
numerator[2],
|
||||
numerator[3],
|
||||
numerator[4],
|
||||
denominator[0],
|
||||
denominator[1],
|
||||
denominator[2],
|
||||
denominator[3],
|
||||
denominator[4],
|
||||
crossoverFrequency,
|
||||
highpass ? "true" : "false",
|
||||
linkwitzRiley ? "4th-Order Linkwitz-Riley" : "2nd-Order Butterworth");
|
||||
} else {
|
||||
// Print coefficients to the console
|
||||
rt_printf("\nNumerator:\t\t%f %f %f\nDenominator:\t\t%f %f %f\nCrossover Frequency:\t%f\nHighpass:\t\t%s\nFilter Type:\t\t%s\n\n",
|
||||
numerator[0],
|
||||
numerator[1],
|
||||
numerator[2],
|
||||
denominator[0],
|
||||
denominator[1],
|
||||
denominator[2],
|
||||
crossoverFrequency,
|
||||
highpass ? "true" : "false",
|
||||
linkwitzRiley ? "4th-Order Linkwitz-Riley" : "2nd-Order Butterworth");
|
||||
}
|
||||
// Allocate memory for delay line based on the number of
|
||||
// coefficients generated. Initialize vectors with values of 0.
|
||||
inputDelayBuf.assign(int(numerator.size()), 0.0);
|
||||
outputDelayBuf.assign(int(denominator.size()), 0.0);
|
||||
// Initialize delay buffer to be two samples long for the
|
||||
// second-order Butterworth filter
|
||||
// Store the delay size of delay buffers
|
||||
inputDelaySize = inputDelayBuf.size();
|
||||
outputDelaySize = outputDelayBuf.size();
|
||||
}
|
||||
|
||||
float applyFilter(float x0) {
|
||||
float applyFilter(const float &x0) {
|
||||
// Increment the write pointer of the delay buffer storing input
|
||||
// samples
|
||||
++inputDelayBufWritePtr;
|
||||
// Wrap values to withink size of buffer. Prevents an integer
|
||||
// overflow
|
||||
inputDelayBufWritePtr = (inputDelayBufWritePtr+inputDelaySize)%inputDelaySize;
|
||||
|
||||
// Increment the write pointer of the delay buffer storing output
|
||||
// samples
|
||||
++outputDelayBufWritePtr;
|
||||
// Wrap values to withink size of buffer. Prevents an integer
|
||||
// overflow
|
||||
outputDelayBufWritePtr = (outputDelayBufWritePtr+outputDelaySize)%outputDelaySize;
|
||||
|
||||
// Set the current value of the input delay buffer to the value of
|
||||
// the sample provided to the function
|
||||
inputDelayBuf[(inputDelayBufWritePtr+inputDelaySize)%inputDelaySize] = x0;
|
||||
|
||||
// Initialize a variable to store an output value
|
||||
float y = 0;
|
||||
// Accumulate each sample in the input delay buffer, multiplied by
|
||||
// it's corresponding coefficient
|
||||
for(unsigned int i = 0; i < inputDelaySize; i++) {
|
||||
y += inputDelayBuf[(inputDelayBufWritePtr-i+inputDelaySize)%inputDelaySize] * numerator[i];
|
||||
}
|
||||
// decumulate each sample in the output delay buffer (aside from
|
||||
// the current index), multiplied by it's corresponding coefficient
|
||||
for(unsigned int i = 1; i < outputDelaySize; i++) {
|
||||
y -= outputDelayBuf[(outputDelayBufWritePtr-i+outputDelaySize)%outputDelaySize] * denominator[i];
|
||||
}
|
||||
// Scale by first coefficient in the denominator (always 1 in
|
||||
// current implementation, so added only for generalization of the
|
||||
// method for future use)
|
||||
y /= denominator[0];
|
||||
|
||||
// Store the calculated output sample in the output sample delay
|
||||
// buffer
|
||||
outputDelayBuf[(outputDelayBufWritePtr+outputDelaySize)%outputDelaySize] = y;
|
||||
|
||||
return y;
|
||||
@@ -95,17 +137,28 @@ class Filter {
|
||||
// Convolution function adapted from: http://stackoverflow.com/questions/24518989/how-to-perform-1-dimensional-valid-convolution
|
||||
template<typename T>
|
||||
std::vector<T> convolve(std::vector<T> const &f, std::vector<T> const &g) {
|
||||
int const nf = f.size();
|
||||
int const ng = g.size();
|
||||
int const n = nf + ng - 1;
|
||||
std::vector<T> out(n, T());
|
||||
for(auto i(0); i < n; ++i) {
|
||||
int const jmn = (i >= ng - 1)? i - (ng - 1) : 0;
|
||||
int const jmx = (i < nf - 1)? i : nf - 1;
|
||||
for(auto j(jmn); j <= jmx; ++j) {
|
||||
out[i] += (f[j] * g[i - j]);
|
||||
// Calculate the size of input vectors
|
||||
int const nf = f.size();
|
||||
int const ng = g.size();
|
||||
// Calculate the size of output vector as the combined size of both
|
||||
// input vectors, minus 1
|
||||
int const n = nf + ng - 1;
|
||||
// Initialize vector of the same input type as input vectors
|
||||
// Allocate memory for all elements of the output to be calculated
|
||||
std::vector<T> out(n, T());
|
||||
// For each output element...
|
||||
for(auto i(0); i < n; ++i) {
|
||||
// Calculate minimum and maximum indexes to iterate over each
|
||||
// vector
|
||||
int const jmn = (i >= ng - 1)? i - (ng - 1) : 0;
|
||||
int const jmx = (i < nf - 1)? i : nf - 1;
|
||||
// Accumulate the multiplication of elements in both vectors,
|
||||
// based on the indexes calculated, to give the output value at
|
||||
// the current output index
|
||||
for(auto j(jmn); j <= jmx; ++j) {
|
||||
out[i] += (f[j] * g[i - j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
+15
-7
@@ -18,6 +18,8 @@
|
||||
#include <getopt.h>
|
||||
#include <Bela.h>
|
||||
|
||||
#include "userOptions.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Handle Ctrl-C by requesting that the audio rendering stop
|
||||
@@ -39,12 +41,15 @@ void usage(const char * processName)
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
BelaInitSettings settings; // Standard audio settings
|
||||
float frequency = 5000.0; // Frequency of crossover
|
||||
|
||||
// Initialize default values for command line options
|
||||
UserOpts uOpts = {1000.0, false};
|
||||
|
||||
struct option customOptions[] =
|
||||
{
|
||||
{"help", 0, NULL, 'h'},
|
||||
{"frequency", 1, NULL, 'f'},
|
||||
{"linkwitzriley", 0, NULL, 'l'},
|
||||
{NULL, 0, NULL, 0}
|
||||
};
|
||||
|
||||
@@ -61,12 +66,15 @@ int main(int argc, char *argv[])
|
||||
usage(basename(argv[0]));
|
||||
exit(0);
|
||||
case 'f':
|
||||
frequency = atof(optarg);
|
||||
if(frequency < 20.0)
|
||||
frequency = 20.0;
|
||||
if(frequency > 5000.0)
|
||||
frequency = 5000.0;
|
||||
uOpts.frequency = atof(optarg);
|
||||
if(uOpts.frequency < 20.0)
|
||||
uOpts.frequency = 20.0;
|
||||
if(uOpts.frequency > 5000.0)
|
||||
uOpts.frequency = 5000.0;
|
||||
break;
|
||||
case 'l':
|
||||
uOpts.linkwitzRiley = true;
|
||||
break;
|
||||
case '?':
|
||||
default:
|
||||
usage(basename(argv[0]));
|
||||
@@ -75,7 +83,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// Initialise the PRU audio device
|
||||
if(Bela_initAudio(&settings, &frequency) != 0) {
|
||||
if(Bela_initAudio(&settings, &uOpts) != 0) {
|
||||
cout << "Error: unable to initialise audio" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
+18
-20
@@ -17,6 +17,7 @@
|
||||
#include <Scope.h>
|
||||
|
||||
#include "filter.h"
|
||||
#include "userOptions.h"
|
||||
|
||||
/* TASK: declare any global variables you need here */
|
||||
|
||||
@@ -29,30 +30,30 @@
|
||||
//
|
||||
// Return true on success; returning false halts the program.
|
||||
|
||||
// Create filter objects using unique pointers for automatic memory
|
||||
// deallocation
|
||||
std::unique_ptr<Filter> gLowPass;
|
||||
std::unique_ptr<Filter> gHighPass;
|
||||
|
||||
// instantiate the scope
|
||||
// Instantiate the scope
|
||||
Scope scope;
|
||||
bool setup(BelaContext *context, void *userData)
|
||||
{
|
||||
|
||||
// Set the Bela IDE scope to read two outputs
|
||||
scope.setup(2, context->audioSampleRate);
|
||||
// Set default values for when command line arguments are not provided by
|
||||
// the user
|
||||
float crossoverFrequency = 1000.0;
|
||||
bool linkwitzRiley = false;
|
||||
// Retrieve a parameter passed in from the initAudio() call
|
||||
if(userData != 0)
|
||||
crossoverFrequency = *(float *)userData;
|
||||
crossoverFrequency = (*(UserOpts *)userData).frequency;
|
||||
linkwitzRiley = (*(UserOpts *)userData).linkwitzRiley;
|
||||
|
||||
/* TASK:
|
||||
* Calculate the filter coefficients based on the given
|
||||
* crossover frequency.
|
||||
*
|
||||
* Initialise any previous state (clearing buffers etc.)
|
||||
* to prepare for calls to render()
|
||||
*/
|
||||
|
||||
gLowPass.reset(new Filter(crossoverFrequency, context->audioSampleRate));
|
||||
gHighPass.reset(new Filter(crossoverFrequency, context->audioSampleRate, true));
|
||||
// Create a low-pass and high-pass filter object using the crossover
|
||||
// frequency and filter design specified by CLI arguments
|
||||
gLowPass.reset(new Filter(crossoverFrequency, context->audioSampleRate, false, linkwitzRiley));
|
||||
gHighPass.reset(new Filter(crossoverFrequency, context->audioSampleRate, true, linkwitzRiley));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -79,11 +80,12 @@ void render(BelaContext *context, void *userData)
|
||||
// Convert input to mono
|
||||
float monoSamp = (leftIn + rightIn) * 0.5;
|
||||
|
||||
// Apply filter to current sample for both channels.
|
||||
// filtering/buffering is handeled within the Filter objects.
|
||||
float leftOut = gLowPass->applyFilter(monoSamp);
|
||||
float rightOut = gHighPass->applyFilter(monoSamp);
|
||||
//leftOut = monoSamp;
|
||||
//rightOut = monoSamp;
|
||||
|
||||
// Plot the output of the filters to the IDE scope
|
||||
scope.log(leftOut, rightOut);
|
||||
// Write the sample into the output buffer
|
||||
audioWrite(context, n, 0, leftOut);
|
||||
@@ -96,9 +98,5 @@ void render(BelaContext *context, void *userData)
|
||||
|
||||
void cleanup(BelaContext *context, void *userData)
|
||||
{
|
||||
/* TASK:
|
||||
* If you allocate any memory, be sure to release it here.
|
||||
* You may or may not need anything in this function, depending
|
||||
* on your implementation.
|
||||
*/
|
||||
// Cleanup wasn't necessary through the use of unique pointers.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user