andrewm@0: /*
andrewm@0:  * render.cpp
andrewm@0:  *
andrewm@0:  *  Created on: Oct 24, 2014
andrewm@0:  *      Author: parallels
andrewm@0:  */
andrewm@0: 
andrewm@0: 
andrewm@0: #include "../../include/RTAudio.h"
andrewm@0: #include "../../include/Utilities.h"
andrewm@0: #include <rtdk.h>
andrewm@0: #include <cstdlib>
andrewm@0: #include <cmath>
andrewm@0: #include <cstring>
andrewm@0: #include <time.h>
andrewm@0: 
andrewm@0: const float kMinimumFrequency = 20.0f;
andrewm@0: const float kMaximumFrequency = 8000.0f;
andrewm@0: 
andrewm@0: float *gWavetable;		// Buffer holding the precalculated sine lookup table
andrewm@0: float *gPhases;			// Buffer holding the phase of each oscillator
andrewm@0: float *gFrequencies;	// Buffer holding the frequencies of each oscillator
andrewm@0: float *gAmplitudes;		// Buffer holding the amplitudes of each oscillator
andrewm@0: float *gDFrequencies;	// Buffer holding the derivatives of frequency
andrewm@0: float *gDAmplitudes;	// Buffer holding the derivatives of amplitude
andrewm@0: 
andrewm@0: float gAudioSampleRate;
andrewm@0: int gSampleCount;		// Sample counter for indicating when to update frequencies
andrewm@0: float gNewMinFrequency;
andrewm@0: float gNewMaxFrequency;
andrewm@0: 
andrewm@0: // Task for handling the update of the frequencies using the matrix
andrewm@0: AuxiliaryTask gFrequencyUpdateTask;
andrewm@0: 
andrewm@0: // These settings are carried over from main.cpp
andrewm@0: // Setting global variables is an alternative approach
andrewm@0: // to passing a structure to userData in initialise_render()
andrewm@0: 
andrewm@0: extern int gNumOscillators;
andrewm@0: extern int gWavetableLength;
andrewm@0: 
andrewm@0: void recalculate_frequencies();
andrewm@0: 
andrewm@0: extern "C" {
andrewm@0: 	// Function prototype for ARM assembly implementation of oscillator bank
andrewm@0: 	void oscillator_bank_neon(int numAudioFrames, float *audioOut,
andrewm@0: 							  int activePartialNum, int lookupTableSize,
andrewm@0: 							  float *phases, float *frequencies, float *amplitudes,
andrewm@0: 							  float *freqDerivatives, float *ampDerivatives,
andrewm@0: 							  float *lookupTable);
andrewm@0: }
andrewm@0: 
andrewm@0: // initialise_render() is called once before the audio rendering starts.
andrewm@0: // Use it to perform any initialisation and allocation which is dependent
andrewm@0: // on the period size or sample rate.
andrewm@0: //
andrewm@0: // userData holds an opaque pointer to a data structure that was passed
andrewm@0: // in from the call to initAudio().
andrewm@0: //
andrewm@0: // Return true on success; returning false halts the program.
andrewm@14: bool initialise_render(int numMatrixChannels, int numAudioChannels,
andrewm@14: 					   int numMatrixFramesPerPeriod,
andrewm@14: 					   int numAudioFramesPerPeriod,
andrewm@14: 					   float matrixSampleRate, float audioSampleRate,
andrewm@14: 					   void *userData)
andrewm@0: {
andrewm@0: 	srandom(time(NULL));
andrewm@0: 
andrewm@14: 	if(numAudioChannels != 2) {
andrewm@14: 		rt_printf("Error: this example needs stereo audio enabled\n");
andrewm@14: 		return false;
andrewm@14: 	}
andrewm@14: 
andrewm@0: 	// Initialise the sine wavetable
andrewm@0: 	if(posix_memalign((void **)&gWavetable, 8, (gWavetableLength + 1) * sizeof(float))) {
andrewm@0: 		rt_printf("Error allocating wavetable\n");
andrewm@0: 		return false;
andrewm@0: 	}
andrewm@0: 	for(int n = 0; n < gWavetableLength + 1; n++)
andrewm@0: 		gWavetable[n] = sinf(2.0 * M_PI * (float)n / (float)gWavetableLength);
andrewm@0: 
andrewm@0: 	// Allocate the other buffers
andrewm@0: 	if(posix_memalign((void **)&gPhases, 16, gNumOscillators * sizeof(float))) {
andrewm@0: 		rt_printf("Error allocating phase buffer\n");
andrewm@0: 		return false;
andrewm@0: 	}
andrewm@0: 	if(posix_memalign((void **)&gFrequencies, 16, gNumOscillators * sizeof(float))) {
andrewm@0: 		rt_printf("Error allocating frequency buffer\n");
andrewm@0: 		return false;
andrewm@0: 	}
andrewm@0: 	if(posix_memalign((void **)&gAmplitudes, 16, gNumOscillators * sizeof(float))) {
andrewm@0: 		rt_printf("Error allocating amplitude buffer\n");
andrewm@0: 		return false;
andrewm@0: 	}
andrewm@0: 	if(posix_memalign((void **)&gDFrequencies, 16, gNumOscillators * sizeof(float))) {
andrewm@0: 		rt_printf("Error allocating frequency derivative buffer\n");
andrewm@0: 		return false;
andrewm@0: 	}
andrewm@0: 	if(posix_memalign((void **)&gDAmplitudes, 16, gNumOscillators * sizeof(float))) {
andrewm@0: 		rt_printf("Error allocating amplitude derivative buffer\n");
andrewm@0: 		return false;
andrewm@0: 	}
andrewm@0: 
andrewm@0: 	// Initialise buffer contents
andrewm@0: 
andrewm@0: 	float freq = kMinimumFrequency;
andrewm@0: 	float increment = (kMaximumFrequency - kMinimumFrequency) / (float)gNumOscillators;
andrewm@0: 
andrewm@0: 	for(int n = 0; n < gNumOscillators; n++) {
andrewm@0: 		gPhases[n] = 0.0;
andrewm@0: 
andrewm@0: 		if(numMatrixFramesPerPeriod == 0) {
andrewm@0: 			// Random frequencies when used without matrix
andrewm@0: 			gFrequencies[n] = kMinimumFrequency + (kMaximumFrequency - kMinimumFrequency) * ((float)random() / (float)RAND_MAX);
andrewm@0: 		}
andrewm@0: 		else {
andrewm@0: 			// Constant spread of frequencies when used with matrix
andrewm@0: 			gFrequencies[n] = freq;
andrewm@0: 			freq += increment;
andrewm@0: 		}
andrewm@0: 
andrewm@0: 		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
andrewm@0: 		gFrequencies[n] *= (float)gWavetableLength / audioSampleRate;
andrewm@0: 		gAmplitudes[n] = ((float)random() / (float)RAND_MAX) / (float)gNumOscillators;
andrewm@0: 		gDFrequencies[n] = gDAmplitudes[n] = 0.0;
andrewm@0: 	}
andrewm@0: 
andrewm@0: 	// Initialise auxiliary tasks
andrewm@0: 	if((gFrequencyUpdateTask = createAuxiliaryTaskLoop(&recalculate_frequencies, 90, "beaglert-update-frequencies")) == 0)
andrewm@0: 		return false;
andrewm@0: 
andrewm@0: 	gAudioSampleRate = audioSampleRate;
andrewm@0: 	gSampleCount = 0;
andrewm@0: 
andrewm@0: 	return true;
andrewm@0: }
andrewm@0: 
andrewm@0: // render() is called regularly at the highest priority by the audio engine.
andrewm@0: // Input and output are given from the audio hardware and the other
andrewm@0: // ADCs and DACs (if available). If only audio is available, numMatrixFrames
andrewm@0: // will be 0.
andrewm@0: 
andrewm@0: void render(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut,
andrewm@0: 			uint16_t *matrixIn, uint16_t *matrixOut)
andrewm@0: {
andrewm@0: 	// Initialise buffer to 0
andrewm@0: 	memset(audioOut, 0, 2 * numAudioFrames * sizeof(float));
andrewm@0: 
andrewm@0: 	// Render audio frames
andrewm@0: 	oscillator_bank_neon(numAudioFrames, audioOut,
andrewm@0: 			gNumOscillators, gWavetableLength,
andrewm@0: 			gPhases, gFrequencies, gAmplitudes,
andrewm@0: 			gDFrequencies, gDAmplitudes,
andrewm@0: 			gWavetable);
andrewm@0: 
andrewm@0: 	if(numMatrixFrames != 0 && (gSampleCount += numAudioFrames) >= 128) {
andrewm@0: 		gSampleCount = 0;
andrewm@0: 		gNewMinFrequency = map(matrixIn[0], 0, MATRIX_MAX, 20.0f, 8000.0f);
andrewm@0: 		gNewMaxFrequency = map(matrixIn[1], 0, MATRIX_MAX, 20.0f, 8000.0f);
andrewm@0: 
andrewm@0: 		// Make sure max >= min
andrewm@0: 		if(gNewMaxFrequency < gNewMinFrequency) {
andrewm@0: 			float temp = gNewMaxFrequency;
andrewm@0: 			gNewMaxFrequency = gNewMinFrequency;
andrewm@0: 			gNewMinFrequency = temp;
andrewm@0: 		}
andrewm@0: 
andrewm@0: 		// Request that the lower-priority task run at next opportunity
andrewm@0: 		scheduleAuxiliaryTask(gFrequencyUpdateTask);
andrewm@0: 	}
andrewm@0: }
andrewm@0: 
andrewm@0: // This is a lower-priority call to update the frequencies which will happen
andrewm@0: // periodically when the matrix is enabled. By placing it at a lower priority,
andrewm@0: // it has minimal effect on the audio performance but it will take longer to
andrewm@0: // complete if the system is under heavy audio load.
andrewm@0: 
andrewm@0: void recalculate_frequencies()
andrewm@0: {
andrewm@0: 	float freq = gNewMinFrequency;
andrewm@0: 	float increment = (gNewMaxFrequency - gNewMinFrequency) / (float)gNumOscillators;
andrewm@0: 
andrewm@0: 	for(int n = 0; n < gNumOscillators; n++) {
andrewm@0: 		// Update the frequencies to a regular spread, plus a small amount of randomness
andrewm@0: 		// to avoid weird phase effects
andrewm@0: 		float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX;
andrewm@0: 		float newFreq = freq * randScale;
andrewm@0: 
andrewm@0: 		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
andrewm@0: 		gFrequencies[n] = newFreq * (float)gWavetableLength / gAudioSampleRate;
andrewm@0: 
andrewm@0: 		freq += increment;
andrewm@0: 	}
andrewm@0: }
andrewm@0: 
andrewm@0: 
andrewm@0: // cleanup_render() is called once at the end, after the audio has stopped.
andrewm@0: // Release any resources that were allocated in initialise_render().
andrewm@0: 
andrewm@0: void cleanup_render()
andrewm@0: {
andrewm@0: 	free(gWavetable);
andrewm@0: 	free(gPhases);
andrewm@0: 	free(gFrequencies);
andrewm@0: 	free(gAmplitudes);
andrewm@0: 	free(gDFrequencies);
andrewm@0: 	free(gDAmplitudes);
andrewm@0: }