robert@464: /*
robert@464:  ____  _____ _        _    
robert@464: | __ )| ____| |      / \   
robert@464: |  _ \|  _| | |     / _ \  
robert@464: | |_) | |___| |___ / ___ \ 
robert@464: |____/|_____|_____/_/   \_\
robert@464: 
robert@464: The platform for ultra-low latency audio and sensor processing
robert@464: 
robert@464: http://bela.io
robert@464: 
robert@464: A project of the Augmented Instruments Laboratory within the
robert@464: Centre for Digital Music at Queen Mary University of London.
robert@464: http://www.eecs.qmul.ac.uk/~andrewm
robert@464: 
robert@464: (c) 2016 Augmented Instruments Laboratory: Andrew McPherson,
robert@464:   Astrid Bin, Liam Donovan, Christian Heinrichs, Robert Jack,
robert@464:   Giulio Moro, Laurel Pardue, Victor Zappi. All rights reserved.
robert@464: 
robert@464: The Bela software is distributed under the GNU Lesser General Public License
robert@464: (LGPL 3.0), available here: https://www.gnu.org/licenses/lgpl-3.0.txt
robert@464: */
robert@464: 
robert@464: 
robert@464: #include <Bela.h>
robert@464: #include <rtdk.h>
robert@464: #include <cstdlib>
robert@464: #include <cmath>
robert@464: #include <cstring>
robert@464: #include <time.h>
robert@464: 
robert@464: const float kMinimumFrequency = 20.0f;
robert@464: const float kMaximumFrequency = 8000.0f;
robert@464: 
robert@464: float *gWavetable;		// Buffer holding the precalculated sine lookup table
robert@464: float *gPhases;			// Buffer holding the phase of each oscillator
robert@464: float *gFrequencies;	// Buffer holding the frequencies of each oscillator
robert@464: float *gAmplitudes;		// Buffer holding the amplitudes of each oscillator
robert@464: float *gDFrequencies;	// Buffer holding the derivatives of frequency
robert@464: float *gDAmplitudes;	// Buffer holding the derivatives of amplitude
robert@464: 
robert@464: float gAudioSampleRate;
robert@464: int gSampleCount;		// Sample counter for indicating when to update frequencies
robert@464: float gNewMinFrequency;
robert@464: float gNewMaxFrequency;
robert@464: 
robert@464: // Task for handling the update of the frequencies using the matrix
robert@464: AuxiliaryTask gFrequencyUpdateTask;
robert@464: 
robert@464: // These settings are carried over from main.cpp
robert@464: // Setting global variables is an alternative approach
robert@464: // to passing a structure to userData in setup()
robert@464: 
robert@464: extern int gNumOscillators;
robert@464: extern int gWavetableLength;
robert@464: 
robert@464: void recalculate_frequencies();
robert@464: 
robert@464: extern "C" {
robert@464: 	// Function prototype for ARM assembly implementation of oscillator bank
robert@464: 	void oscillator_bank_neon(int numAudioFrames, float *audioOut,
robert@464: 							  int activePartialNum, int lookupTableSize,
robert@464: 							  float *phases, float *frequencies, float *amplitudes,
robert@464: 							  float *freqDerivatives, float *ampDerivatives,
robert@464: 							  float *lookupTable);
robert@464: }
robert@464: 
robert@464: // setup() is called once before the audio rendering starts.
robert@464: // Use it to perform any initialisation and allocation which is dependent
robert@464: // on the period size or sample rate.
robert@464: //
robert@464: // userData holds an opaque pointer to a data structure that was passed
robert@464: // in from the call to initAudio().
robert@464: //
robert@464: // Return true on success; returning false halts the program.
robert@464: bool setup(BelaContext *context, void *userData)
robert@464: {
robert@464: 	srandom(time(NULL));
robert@464: 
robert@464: 	if(context->audioChannels != 2) {
robert@464: 		rt_printf("Error: this example needs stereo audio enabled\n");
robert@464: 		return false;
robert@464: 	}
robert@464: 
robert@464: 	// Initialise the sine wavetable
robert@464: 	if(posix_memalign((void **)&gWavetable, 8, (gWavetableLength + 1) * sizeof(float))) {
robert@464: 		rt_printf("Error allocating wavetable\n");
robert@464: 		return false;
robert@464: 	}
robert@464: 	for(int n = 0; n < gWavetableLength + 1; n++)
robert@464: 		gWavetable[n] = sinf(2.0 * M_PI * (float)n / (float)gWavetableLength);
robert@464: 
robert@464: 	// Allocate the other buffers
robert@464: 	if(posix_memalign((void **)&gPhases, 16, gNumOscillators * sizeof(float))) {
robert@464: 		rt_printf("Error allocating phase buffer\n");
robert@464: 		return false;
robert@464: 	}
robert@464: 	if(posix_memalign((void **)&gFrequencies, 16, gNumOscillators * sizeof(float))) {
robert@464: 		rt_printf("Error allocating frequency buffer\n");
robert@464: 		return false;
robert@464: 	}
robert@464: 	if(posix_memalign((void **)&gAmplitudes, 16, gNumOscillators * sizeof(float))) {
robert@464: 		rt_printf("Error allocating amplitude buffer\n");
robert@464: 		return false;
robert@464: 	}
robert@464: 	if(posix_memalign((void **)&gDFrequencies, 16, gNumOscillators * sizeof(float))) {
robert@464: 		rt_printf("Error allocating frequency derivative buffer\n");
robert@464: 		return false;
robert@464: 	}
robert@464: 	if(posix_memalign((void **)&gDAmplitudes, 16, gNumOscillators * sizeof(float))) {
robert@464: 		rt_printf("Error allocating amplitude derivative buffer\n");
robert@464: 		return false;
robert@464: 	}
robert@464: 
robert@464: 	// Initialise buffer contents
robert@464: 
robert@464: 	float freq = kMinimumFrequency;
robert@464: 	float increment = (kMaximumFrequency - kMinimumFrequency) / (float)gNumOscillators;
robert@464: 
robert@464: 	for(int n = 0; n < gNumOscillators; n++) {
robert@464: 		gPhases[n] = 0.0;
robert@464: 
robert@464: 		if(context->analogFrames == 0) {
robert@464: 			// Random frequencies when used without matrix
robert@464: 			gFrequencies[n] = kMinimumFrequency + (kMaximumFrequency - kMinimumFrequency) * ((float)random() / (float)RAND_MAX);
robert@464: 		}
robert@464: 		else {
robert@464: 			// Constant spread of frequencies when used with matrix
robert@464: 			gFrequencies[n] = freq;
robert@464: 			freq += increment;
robert@464: 		}
robert@464: 
robert@464: 		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
robert@464: 		gFrequencies[n] *= (float)gWavetableLength / context->audioSampleRate;
robert@464: 		gAmplitudes[n] = ((float)random() / (float)RAND_MAX) / (float)gNumOscillators;
robert@464: 		gDFrequencies[n] = gDAmplitudes[n] = 0.0;
robert@464: 	}
robert@464: 
robert@464: 	increment = 0;
robert@464: 	freq = 440.0;
robert@464: 
robert@464: 	for(int n = 0; n < gNumOscillators; n++) {
robert@464: 		// Update the frequencies to a regular spread, plus a small amount of randomness
robert@464: 		// to avoid weird phase effects
robert@464: 		float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX;
robert@464: 		float newFreq = freq * randScale;
robert@464: 
robert@464: 		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
robert@464: 		gFrequencies[n] = newFreq * (float)gWavetableLength / context->audioSampleRate;
robert@464: 
robert@464: 		freq += increment;
robert@464: 	}
robert@464: 
robert@464: 	// Initialise auxiliary tasks
robert@464: 	if((gFrequencyUpdateTask = Bela_createAuxiliaryTask(&recalculate_frequencies, 85, "bela-update-frequencies")) == 0)
robert@464: 		return false;
robert@464: 
robert@464: 	//for(int n = 0; n < gNumOscillators; n++)
robert@464: 	//	rt_printf("%f\n", gFrequencies[n]);
robert@464: 
robert@464: 	gAudioSampleRate = context->audioSampleRate;
robert@464: 	gSampleCount = 0;
robert@464: 
robert@464: 	return true;
robert@464: }
robert@464: 
robert@464: // render() is called regularly at the highest priority by the audio engine.
robert@464: // Input and output are given from the audio hardware and the other
robert@464: // ADCs and DACs (if available). If only audio is available, numMatrixFrames
robert@464: // will be 0.
robert@464: 
robert@464: void render(BelaContext *context, void *userData)
robert@464: {
robert@464: 	// Initialise buffer to 0
robert@464: 	memset(context->audioOut, 0, 2 * context->audioFrames * sizeof(float));
robert@464: 
robert@464: 	// Render audio frames
robert@464: 	oscillator_bank_neon(context->audioFrames, context->audioOut,
robert@464: 			gNumOscillators, gWavetableLength,
robert@464: 			gPhases, gFrequencies, gAmplitudes,
robert@464: 			gDFrequencies, gDAmplitudes,
robert@464: 			gWavetable);
robert@464: 
robert@464: 	if(context->analogFrames != 0 && (gSampleCount += context->audioFrames) >= 128) {
robert@464: 		gSampleCount = 0;
robert@464: 		gNewMinFrequency = map(context->analogIn[0], 0, 1.0, 1000.0f, 8000.0f);
robert@464: 		gNewMaxFrequency = map(context->analogIn[1], 0, 1.0, 1000.0f, 8000.0f);
robert@464: 
robert@464: 		// Make sure max >= min
robert@464: 		if(gNewMaxFrequency < gNewMinFrequency) {
robert@464: 			float temp = gNewMaxFrequency;
robert@464: 			gNewMaxFrequency = gNewMinFrequency;
robert@464: 			gNewMinFrequency = temp;
robert@464: 		}
robert@464: 
robert@464: 		// Request that the lower-priority task run at next opportunity
robert@464: 		//Bela_scheduleAuxiliaryTask(gFrequencyUpdateTask);
robert@464: 	}
robert@464: }
robert@464: 
robert@464: // This is a lower-priority call to update the frequencies which will happen
robert@464: // periodically when the matrix is enabled. By placing it at a lower priority,
robert@464: // it has minimal effect on the audio performance but it will take longer to
robert@464: // complete if the system is under heavy audio load.
robert@464: 
robert@464: void recalculate_frequencies()
robert@464: {
robert@464: 	float freq = gNewMinFrequency;
robert@464: 	float increment = (gNewMaxFrequency - gNewMinFrequency) / (float)gNumOscillators;
robert@464: 
robert@464: 	for(int n = 0; n < gNumOscillators; n++) {
robert@464: 		// Update the frequencies to a regular spread, plus a small amount of randomness
robert@464: 		// to avoid weird phase effects
robert@464: 		float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX;
robert@464: 		float newFreq = freq * randScale;
robert@464: 
robert@464: 		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
robert@464: 		gFrequencies[n] = newFreq * (float)gWavetableLength / gAudioSampleRate;
robert@464: 
robert@464: 		freq += increment;
robert@464: 	}
robert@464: }
robert@464: 
robert@464: 
robert@464: // cleanup() is called once at the end, after the audio has stopped.
robert@464: // Release any resources that were allocated in setup().
robert@464: 
robert@464: void cleanup(BelaContext *context, void *userData)
robert@464: {
robert@464: 	free(gWavetable);
robert@464: 	free(gPhases);
robert@464: 	free(gFrequencies);
robert@464: 	free(gAmplitudes);
robert@464: 	free(gDFrequencies);
robert@464: 	free(gDAmplitudes);
robert@464: }
robert@464: 
robert@464: /* ------------ Project Explantation ------------ */
robert@464: 
robert@464: /**
robert@464: \example 04-oscillator-bank
robert@464: 
robert@464: Oscillator Bank
robert@464: ----------------------
robert@464: 
robert@464: These files demonstrate an oscillator bank implemented in assembly code 
robert@464: that is used as part of the d-box project.
robert@464: */