This sketch displays and sonifies the values of up to 64 analog inputs connected by the multiplexer capelet, after lowpass filtering. The capelet is a separate piece of hardware that attaches to the Bela cape.
This sketch demonstrates the use of frame-by-frame querying of the multiplexer capelet. When enabled, each analog frame represents a different multiplexer setting. This sketch checks each frame and assigns it to the correct filter.
As a demo, the amplitudes of each multiplexer input are used to control the amplitudes of a bank of harmonically-tuned oscillators. You can hear the effect by taking a wire connected to 5V or 3.3V, holding it in one hand while running your finger along the (otherwise unconnected) inputs to the multiplexer capelet. Alternatively, you can hook up each input to a separate control.
To run the sketch, the multiplexer capelet needs to be enabled using the IDE or with the -X command line option. The multiplexer capelet requires 8 analog inputs to work, and depending on the settings can use either 2, 4 or 8 multiplexer channels per analog input (for a total of 16, 32 or 64 inputs).
The sample rate for each multiplexed input will be 11.025kHz (16 inputs), 5.5kHz (32 inputs) or 2.75kHz (64 inputs).
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <unistd.h>
float *gFilterLastInputs;
float *gFilterLastOutputs;
unsigned long long *gFilterSampleCounts;
float gCoeffB0 = 0, gCoeffB1 = 0, gCoeffB2 = 0, gCoeffA1 = 0, gCoeffA2 = 0;
unsigned int gPrintCount = 0;
unsigned int gAnalogInChannels = 0, gMultiplexerChannels = 0;
int gNumOscillators = 0;
int gWavetableLength = 1024;
float gAudioSampleRate = 44100.0;
unsigned long long gAudioFramesElapsed = 0;
bool gIsStdoutTty;
float *gWavetable;
float *gPhases;
float *gFrequencies;
float *gAmplitudes;
float *gDFrequencies;
float *gDAmplitudes;
void calculate_coeff(float sampleRate, float cutFreq);
bool initialise_oscillators(float fundamental_frequency);
void print_results(void*);
extern "C" {
void oscillator_bank_neon(int numAudioFrames, float *audioOut,
int activePartialNum, int lookupTableSize,
float *phases, float *frequencies, float *amplitudes,
float *freqDerivatives, float *ampDerivatives,
float *lookupTable);
}
{
rt_printf("Error: this example needs stereo audio enabled\n");
return false;
}
rt_printf("Please enable the Multiplexer Capelet to run this example.\n");
return false;
}
gFilterLastInputs = (float *)malloc(2 * totalInputs * sizeof(float));
gFilterLastOutputs = (float *)malloc(2 * totalInputs * sizeof(float));
if(gFilterLastInputs == 0 || gFilterLastOutputs == 0) {
rt_printf("Unable to allocate memory buffers.\n");
return false;
}
memset(gFilterLastInputs, 0, 2 * totalInputs * sizeof(float));
memset(gFilterLastOutputs, 0, 2 * totalInputs * sizeof(float));
gFilterSampleCounts = (unsigned long long *)malloc(totalInputs * sizeof(unsigned long long));
if(gFilterSampleCounts == 0) {
rt_printf("Unable to allocate memory buffers.\n");
return false;
}
memset(gFilterSampleCounts, 0, totalInputs * sizeof(unsigned long long));
gNumOscillators = totalInputs;
if(!initialise_oscillators(55.0))
return false;
return false;
gIsStdoutTty = isatty(1);
return true;
}
{
float output = gCoeffB0 * input
+ gCoeffB1 * gFilterLastInputs[filterIndex * 2]
+ gCoeffB2 * gFilterLastInputs[filterIndex * 2 + 1]
- gCoeffA1 * gFilterLastOutputs[filterIndex * 2]
- gCoeffA2 * gFilterLastOutputs[filterIndex * 2 + 1];
gFilterLastInputs[filterIndex*2 + 1] = gFilterLastInputs[filterIndex*2];
gFilterLastInputs[filterIndex*2] = input;
gFilterLastOutputs[filterIndex*2 + 1] = gFilterLastOutputs[filterIndex*2];
gFilterLastOutputs[filterIndex*2] = output;
gFilterSampleCounts[filterIndex]++;
gAmplitudes[filterIndex] = output * output / 4.f;
}
}
gNumOscillators, gWavetableLength,
gPhases, gFrequencies, gAmplitudes,
gDFrequencies, gDAmplitudes,
gWavetable);
gPrintCount = 0;
}
}
{
free(gFilterLastInputs);
free(gFilterLastOutputs);
free(gFilterSampleCounts);
free(gWavetable);
free(gPhases);
free(gFrequencies);
free(gAmplitudes);
free(gDFrequencies);
free(gDAmplitudes);
}
void calculate_coeff(float sampleRate, float cutFreq)
{
double f = 2*M_PI*cutFreq/sampleRate;
double denom = 4+2*sqrt(2)*f+f*f;
gCoeffB0 = f*f/denom;
gCoeffB1 = 2*gCoeffB0;
gCoeffB2 = gCoeffB0;
gCoeffA1 = (2*f*f-8)/denom;
gCoeffA2 = (f*f+4-2*sqrt(2)*f)/denom;
}
bool initialise_oscillators(float fundamental_frequency)
{
if(posix_memalign((void **)&gWavetable, 8, (gWavetableLength + 1) * sizeof(float))) {
rt_printf("Error allocating wavetable\n");
return false;
}
for(int n = 0; n < gWavetableLength + 1; n++)
gWavetable[n] = sinf(2.0 * M_PI * (float)n / (float)gWavetableLength);
if(posix_memalign((void **)&gPhases, 16, gNumOscillators * sizeof(float))) {
rt_printf("Error allocating phase buffer\n");
return false;
}
if(posix_memalign((void **)&gFrequencies, 16, gNumOscillators * sizeof(float))) {
rt_printf("Error allocating frequency buffer\n");
return false;
}
if(posix_memalign((void **)&gAmplitudes, 16, gNumOscillators * sizeof(float))) {
rt_printf("Error allocating amplitude buffer\n");
return false;
}
if(posix_memalign((void **)&gDFrequencies, 16, gNumOscillators * sizeof(float))) {
rt_printf("Error allocating frequency derivative buffer\n");
return false;
}
if(posix_memalign((void **)&gDAmplitudes, 16, gNumOscillators * sizeof(float))) {
rt_printf("Error allocating amplitude derivative buffer\n");
return false;
}
for(int n = 0; n < gNumOscillators; n++) {
gPhases[n] = (float)random() / (float)RAND_MAX;
gFrequencies[n] = fundamental_frequency * powf(1.0 + n, 1.002) * (float)gWavetableLength / 44100.0;
gAmplitudes[n] = 0.0;
gDFrequencies[n] = gDAmplitudes[n] = 0.0;
}
gAmplitudes[3] = 0.5;
return true;
}
void print_results(void*)
{
if(gIsStdoutTty)
rt_printf("\e[1;1H\e[2J");
for(unsigned int input = 0; input < gAnalogInChannels; input++) {
rt_printf("Input %d: ", input);
for(unsigned int muxChannel = 0; muxChannel < gMultiplexerChannels; muxChannel++) {
int filterIndex = input * gMultiplexerChannels + muxChannel;
float sample = gFilterLastOutputs[2*filterIndex];
rt_printf("%f ", sample);
}
rt_printf("\n");
}
}