I'm trying to setup a project based on lecture 20 of the "C++ Real-Time Audio Programming with Bela" on YouTube [

in which the "whisperisation" and "robotisation v2" effects can be toggled with the press of a button. Additionally, I want to have it so that a potentiometer controls either the base frequency when in robotisation mode or the hop size in whisperisation mode. However when I try to run the project I get the following errors:
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 0) >= this->size() (which is 0)
Aborted

As far as I can tell this error typically occurs if you try to index a vector beyond its bounds, but I can't find an instance in the code where this would happen. If anyone can point out where that may be the case, or suggest another explanation, that would be much welcomed. Also please bear in mind that I'm new to c++ and Bela when providing answers 🙂

Apologies for not including the render file in the initial post, having some issues with getting it uploaded. Will post as a reply when I can.

#include <Bela.h>
#include <libraries/Fft/Fft.h>
#include <libraries/Scope/Scope.h>
#include <libraries/Gui/Gui.h>
#include <libraries/GuiController/GuiController.h>
#include <cmath>
#include <cstring>
#include <vector>
#include <algorithm>
#include "MonoFilePlayer.h"

// FFT-related variables
Fft gFft;							// FFT processing object
const int gFftSize = 1024;			// FFT window size in samples
int gHopSize = 128;					// How often we calculate a window
float gScaleFactor = 0.25;			// How much to scale the output, based on window type and overlap
float gBaseFrequency = 110;			// Fundamental frequency of the robot effect
float gSampleRate = 44100;			// Sample rate (updated in setup())

// Circular buffer and pointer for assembling a window of samples
const int gBufferSize = 16384;
std::vector<float> gInputBuffer;
int gInputBufferPointer = 0;
int gHopCounter = 0;

// Circular buffer for collecting the output of the overlap-add process
std::vector<float> gOutputBuffer;

// Start the write pointer ahead of the read pointer by at least window + hop, with some margin
int gOutputBufferWritePointer = gFftSize + 2*gHopSize;
int gOutputBufferReadPointer = 0;

// Buffer to hold the windows for FFT analysis and synthesis
std::vector<float> gAnalysisWindowBuffer;
std::vector<float> gSynthesisWindowBuffer;

std::vector<float> gBinFrequencies(gFftSize/2 + 1);

// Name of the sound file (in project folder)
std::string gFilename = "Poem.wav"; 

// Object that handles playing sound from a buffer
MonoFilePlayer gPlayer;

// Thread for FFT processing
AuxiliaryTask gFftTask;
int gCachedInputBufferPointer = 0;

void process_fft_background(void *);

// Bela oscilloscope
Scope gScope;

// Browser-based GUI to adjust parameters
Gui gGui;
GuiController gGuiController;

int mode = 0;

// Analog inputs
float gPot1;
float gPot2;

// Digital inputs
int gButton = 1;
int gLastButtonStatus = HIGH;
std::vector<int> gNum = {0, 1};  // vector to cylce through that chooses the filter type
unsigned int gNumLocation = 0;      // pointer for vector

int gAudioFramesPerAnalogFrame = 0;

float potValue = 0;
// Function to recalculate the window based on hop size
void recalculate_window(unsigned int length)
{
	// Check for running off the end of the buffer
	if(length > gAnalysisWindowBuffer.size())
		length = gAnalysisWindowBuffer.size();
	if(length > gSynthesisWindowBuffer.size())
		length = gSynthesisWindowBuffer.size();
	
	for(int n = 0; n < gFftSize; n++) {
		// Hann window
		gAnalysisWindowBuffer[n] = 0.5f * (1.0f - cosf(2.0 * M_PI * n / (float)(gFftSize - 1)));
		gSynthesisWindowBuffer[n] = gAnalysisWindowBuffer[n];	

	}	
}

bool setup(BelaContext *context, void *userData)
{
	// Load the audio file
	if(!gPlayer.setup(gFilename)) {
    	rt_printf("Error loading audio file '%s'\n", gFilename.c_str());
    	return false;
	}

	// Print some useful info
    rt_printf("Loaded the audio file '%s' with %d frames (%.1f seconds)\n", 
    			gFilename.c_str(), gPlayer.size(),
    			gPlayer.size() / context->audioSampleRate);
    			
    // Cache the sample rate for use in process_fft()
    gSampleRate = context->audioSampleRate;
	
	// Set up the FFT and its buffers
	gFft.setup(gFftSize);
	gInputBuffer.resize(gBufferSize);
	gOutputBuffer.resize(gBufferSize);
	
	// Calculate the windows
	gAnalysisWindowBuffer.resize(gFftSize);
	gSynthesisWindowBuffer.resize(gFftSize);
	recalculate_window(gFftSize);
	
	
	// Precompute the bin frequencies
	for(int n = 0; n <= gFftSize/2; n++) {
		gBinFrequencies[n] = 2.0 * M_PI * (float)n / (float)gFftSize;
	}
	
	// Initialise the oscilloscope
	gScope.setup(2, context->audioSampleRate);
	
	// Set up the GUI
	//gGui.setup(context->projectName);
	//gGuiController.setup(&gGui, "Robotisation Controller");	
	
	// Arguments: name, minimum, maximum, increment, default value
	//gGuiController.addSlider("Frequency", 220, 55.5, 440, 0);
	
	// Set up the thread for the FFT
	gFftTask = Bela_createAuxiliaryTask(process_fft_background, 50, "bela-process-fft");
	
	pinMode(context, 0, gButton, INPUT); //set input
	
	// check audio and digital have same no. frames
	if(context->audioFrames != context->digitalFrames) {
		rt_fprintf(stderr, "This example needs audio and digital running at the same rate.\n");
		return false;
	}
	
	// calculate no. audio frames per analog frame
	if(context->analogFrames)
		gAudioFramesPerAnalogFrame = context->audioFrames / context->analogFrames;

	return true;
}

// Wrap the phase to the range -pi to pi
float wrapPhase(float phaseIn)
{
    if (phaseIn >= 0)
        return fmodf(phaseIn + M_PI, 2.0 * M_PI) - M_PI;
    else
        return fmodf(phaseIn - M_PI, -2.0 * M_PI) + M_PI;	
}
// This function handles the FFT processing in this example once the buffer has
// been assembled.

void process_fft(std::vector<float> const& inBuffer, unsigned int inPointer, std::vector<float>& outBuffer, unsigned int outPointer, int mode, float potValue)
{
	static std::vector<float> unwrappedBuffer(gFftSize);	// Container to hold the unwrapped time-domain values
	
	static std::vector<float> lastInputPhases(gFftSize);	// Hold the phases from the previous hop of input signal
	static std::vector<float> lastOutputPhases(gFftSize);	// and output (synthesised) signal
	
	// These containers hold the converted representation from magnitude-phase
	// into magnitude-frequency, used for pitch shifting
	static std::vector<float> analysisMagnitudes(gFftSize / 2 + 1);
	static std::vector<float> analysisFrequencies(gFftSize / 2 + 1);
	static std::vector<float> synthesisMagnitudes(gFftSize / 2 + 1);
	static std::vector<float> synthesisFrequencies(gFftSize / 2 + 1);
	static std::vector<int> synthesisCount(gFftSize / 2 + 1);
	
	// Copy buffer into FFT input
	for(int n = 0; n < gFftSize; n++) {
		// Use modulo arithmetic to calculate the circular buffer index
		int circularBufferIndex = (inPointer + n - gFftSize + gBufferSize) % gBufferSize;
		unwrappedBuffer[n] = inBuffer[circularBufferIndex] * gAnalysisWindowBuffer[n];
	}
	
	// Process the FFT based on the time domain input
	gFft.fft(unwrappedBuffer);

Note that this function is too long to fit into a single message and so has been split into multiple parts
1/3

if(mode==0){
		gBaseFrequency = potValue;
		
		// Analyse the lower half of the spectrum. The upper half is just
		// the complex conjugate and does not contain any unique information
		for(int n = 0; n <= gFftSize/2; n++) {
			// Turn real and imaginary components into amplitude and phase
			float amplitude = gFft.fda(n);
			float phase = atan2f(gFft.fdi(n), gFft.fdr(n));
		
			// Calculate the phase difference in this bin between the last
			// hop and this one, which will indirectly give us the exact frequency
			float phaseDiff = phase - lastInputPhases[n];
		
			// Subtract the amount of phase increment we'd expect to see based
			// on the centre frequency of this bin (2*pi*n/gFftSize) for this
			// hop size, then wrap to the range -pi to pi
			phaseDiff = wrapPhase(phaseDiff - gBinFrequencies[n] * gHopSize);
		
			// Find deviation from the centre frequency
			float frequencyDeviation = phaseDiff / (float)gHopSize;
		
			// Add the original bin number to get the fractional bin where this partial belongs
			analysisFrequencies[n] = ((float)n * 2.0 * M_PI / (float)gFftSize) + frequencyDeviation;
		
			// Save the magnitude for later and for the GUI
			analysisMagnitudes[n] = amplitude;
		
			// Save the phase for next hop
			lastInputPhases[n] = phase;
		}	

		// Zero out the synthesis bins, ready for new data
		for(int n = 0; n <= gFftSize/2; n++) {
			synthesisMagnitudes[n] = synthesisFrequencies[n] = synthesisCount[n] = 0;
		}
	
		// Handle the robotisation effect, storing frequencies into new bins
		for(int n = 0; n <= gFftSize/2; n++) {
			// Round the frequency to the nearest multiple of the fundamental
			float harmonicRaw = analysisFrequencies[n]*gSampleRate/(2.0*M_PI)/gBaseFrequency;
			float harmonicBelow = floorf(harmonicRaw);
			float harmonicAbove = harmonicBelow + 1;
			float harmonicFraction = harmonicRaw - harmonicBelow;

			// Only resynthesise frequenices greater than 0
			if(harmonicBelow > 0) {
				float newFrequency = gBaseFrequency*(2.0*M_PI/gSampleRate)*harmonicBelow;
			
				// Find the nearest bin to the shifted frequency
				int newBin = floorf(newFrequency*(float)gFftSize/(2.0*M_PI) + 0.5);
			
				// Ignore any bins that have shifted above Nyquist
				if(newBin <= gFftSize/2) {
					synthesisMagnitudes[newBin] += analysisMagnitudes[n]*(1.0 - harmonicFraction);
					synthesisFrequencies[newBin] = newFrequency;
				}
			}
		
			// harmonicAbove is always > 0
			float newFrequency = gBaseFrequency*(2.0*M_PI/gSampleRate)*harmonicAbove;
		
			// Find the nearest bin to the shifted frequency
			int newBin = floorf(newFrequency*(float)gFftSize/(2.0*M_PI) + 0.5);
		
			// Ignore any bins that have shifted above Nyquist
			if(newBin <= gFftSize/2) {
				synthesisMagnitudes[newBin] += analysisMagnitudes[n]*harmonicFraction;
				synthesisFrequencies[newBin] = newFrequency;
			}
		}
		
		// Synthesise frequencies into new magnitude and phase values for FFT bins
		for(int n = 0; n <= gFftSize / 2; n++) {
			// Get the fractional offset from the bin centre frequency
			float frequencyDeviation = synthesisFrequencies[n] - ((float)n * 2.0 * M_PI / (float)gFftSize);
		
			// Multiply to get back to a phase value
			float phaseDiff = frequencyDeviation * (float)gHopSize;
		
			// Add the expected phase increment based on the bin centre frequency
			phaseDiff +=  gBinFrequencies[n] * gHopSize;
		
			// Advance the phase from the previous hop
			float outPhase = wrapPhase(lastOutputPhases[n] + phaseDiff);
		
			// Now convert magnitude and phase back to real and imaginary components
			gFft.fdr(n) = synthesisMagnitudes[n] * cosf_neon(outPhase);
			gFft.fdi(n) = synthesisMagnitudes[n] * sinf_neon(outPhase);
		
			// Also store the complex conjugate in the upper half of the spectrum
			if(n > 0 && n < gFftSize / 2) {
				gFft.fdr(gFftSize - n) = gFft.fdr(n);
				gFft.fdi(gFftSize - n) = -gFft.fdi(n);
			}
		
			// Save the phase for the next hop
			lastOutputPhases[n] = outPhase;
		}
	}

2/3

if(mode==1){
		for(int n = 0; n < gFftSize; n++) {
			float amplitude = gFft.fda(n);
			float phase = 2.0*M_PI*(float)rand()/(float)RAND_MAX;
			gFft.fdr(n) = amplitude*cosf_neon(phase);
			gFft.fdi(n) = amplitude*sinf_neon(phase);
		}
	}
	
		
	// Run the inverse FFT
	gFft.ifft();
	
	// Add timeDomainOut into the output buffer
	for(int n = 0; n < gFftSize; n++) {
		int circularBufferIndex = (outPointer + n - gFftSize + gBufferSize) % gBufferSize;
		outBuffer[circularBufferIndex] += gFft.td(n) * gSynthesisWindowBuffer[n];
	}
}

3/3

// This function runs in an auxiliary task on Bela, calling process_fft
void process_fft_background(void *)
{
	process_fft(gInputBuffer, gCachedInputBufferPointer, gOutputBuffer, gOutputBufferWritePointer, mode, potValue);

	// Update the output buffer write pointer to start at the next hop
	gOutputBufferWritePointer = (gOutputBufferWritePointer + gHopSize) % gBufferSize;
}

void render(BelaContext *context, void *userData)
{
	// Get the pitch shift in semitones from the GUI slider and convert to ratio
	gBaseFrequency = gGuiController.getSliderValue(0);
	
	for(unsigned int n = 0; n < context->audioFrames; n++) {
        gPot1 = analogRead(context, n/gAudioFramesPerAnalogFrame, 0);
        
        // detect falling edge of button and cycle through effects
		int status = digitalRead(context, n, gButton);
		
		if(status == LOW && gLastButtonStatus == HIGH){
			gNumLocation++;
			if(gNumLocation >= gNum.size()){
				gNumLocation = 0;
			}
		}
		gLastButtonStatus = status;
		mode = gNum[gNumLocation];
		
		if(mode==0){
			gHopSize = 128;
			potValue = map(gPot1, 0, 0.8, 55, 440);
		}
		if(mode==1){
			potValue = map(gPot1, 0, 0.8, 64, 256);
			int hopSize = floorf(potValue + 0.5);
			// If hop size changes, recalculate the window based on an overlap of 4
			if(hopSize != gHopSize){
				int newLength = hopSize*4;
				if(newLength > gFftSize)
					newLength = gFftSize;
				recalculate_window(newLength);
		
				gHopSize = hopSize;
			}
		}
		// Read the next sample from the buffer
        float in = gPlayer.process();
        //float in = 0.5*audioRead(context, n, 0) + 0.5*audioRead(context, n, 1);
		
		// Store the sample ("in") in a buffer for the FFT
		// Increment the pointer and when full window has been 
		// assembled, call process_fft()
		gInputBuffer[gInputBufferPointer++] = in;
		if(gInputBufferPointer >= gBufferSize) {
			// Wrap the circular buffer
			// Notice: this is not the condition for starting a new FFT
			gInputBufferPointer = 0;
		}
		
		// Get the output sample from the output buffer
		float out = gOutputBuffer[gOutputBufferReadPointer];
		
		// Then clear the output sample in the buffer so it is ready for the next overlap-add
		gOutputBuffer[gOutputBufferReadPointer] = 0;
		
		// Scale the output down by the scale factor, compensating for the overlap
		out *= (float)gHopSize / (float)gFftSize;
		
		// Increment the read pointer in the output cicular buffer
		gOutputBufferReadPointer++;
		if(gOutputBufferReadPointer >= gBufferSize)
			gOutputBufferReadPointer = 0;
		
		// Increment the hop counter and start a new FFT if we've reached the hop size
		if(++gHopCounter >= gHopSize) {
			gHopCounter = 0;
			
			gCachedInputBufferPointer = gInputBufferPointer;
			Bela_scheduleAuxiliaryTask(gFftTask);
		}

		// Write the audio to the output
		for(unsigned int channel = 0; channel < context->audioOutChannels; channel++) {
			audioWrite(context, n, channel, out);
		}
		
		// Log to the Scope
		gScope.log(in, out);
	}
}

void cleanup(BelaContext *context, void *userData)
{

}

Again apologies about having to upload the project in this way but it seems like the only way to get it to work

the routine way of proceeding here would be to run the program inside a debugger which will help you find the place where the exception is thrown from. The most expedient (although not pretty) way to do this is to follow the instructions here https://forum.bela.io/d/3303-using-rotary-encoders-via-i2c/6

I ran your program like that, when it stops I type bt and if I scroll down I see

#11 GuiController::getSliderValue (this=,  sliderIndex=) at libraries/GuiController/GuiController.cpp:104
#12 0x0005029c in render (context=0x6e868 , userData=) at /root/Bela/projects/fft/render.cpp:318

so the exception is thrown inside calling guiController.getSliderValue(0) . Looking at your code, you commented out the guiController.setup() and guiController.addSlider() calls and so you are trying to access a slider that has not been created.

Again apologies about having to upload the project in this way but it seems like the only way to get it to work

Yeah something weird with the forum ... anyhow for such long files (and projects with multiple files especially), it's perhaps always best to upload it somewhere else ( e.g.: github, or google drive) and link to it here.

Thanks very much, it builds and runs now. 🙂