view projects/d-box/render.cpp @ 39:638bc1ae2500 staging

Improved readibility of the DIGITAL code in the PRU, using register names instead of aliases and expanding some of the macros, removing unused macros. Binaries were not modified
author Giulio Moro <giuliomoro@yahoo.it>
date Wed, 13 May 2015 12:18:10 +0100
parents 06f93bef7dd2
children 42a683058b6a
line wrap: on
line source
/*
 * render.cpp
 *
 *  Created on: May 28, 2014
 *      Author: Victor Zappi
 */

#include "../../include/RTAudio.h"
#include "../../include/PRU.h"
#include "StatusLED.h"
#include "config.h"
#include "OscillatorBank.h"
#include "FeedbackOscillator.h"
#include "ADSR.h"
#include "FIRfilter.h"
#include <assert.h>
#include <cmath>
#include <vector>

#undef DBOX_CAPE_TEST

#define N_OCT		4.0	// maximum number of octaves on sensor 1

extern vector<OscillatorBank*> gOscBanks;
extern int gCurrentOscBank;
extern int gNextOscBank;
extern PRU *gPRU;
extern StatusLED gStatusLED;
extern bool gIsLoading;
extern bool gAudioIn;
extern int gPeriodSize;

float *gOscillatorBuffer1, *gOscillatorBuffer2;
float *gOscillatorBufferRead, *gOscillatorBufferWrite;
int gOscillatorBufferReadPointer		= 0;
int gOscillatorBufferReadCurrentSize	= 0;
int gOscillatorBufferWriteCurrentSize	= 0;
bool gOscillatorNeedsRender				= false;

int gMatrixSampleCount = 0;		// How many samples have elapsed on the matrix

// Wavetable which changes in response to an oscillator
float *gDynamicWavetable;
int gDynamicWavetableLength;
bool gDynamicWavetableNeedsRender = false;

// These variables handle the hysteresis oscillator used for setting the playback speed
bool gSpeedHysteresisOscillatorRising	= false;
int gSpeedHysteresisLastTrigger			= 0;

// These variables handle the feedback oscillator used for controlling the wavetable
FeedbackOscillator gFeedbackOscillator;
float *gFeedbackOscillatorTable;
int gFeedbackOscillatorTableLength;

// This comes from sensor.cpp where it records the most recent touch location on
// sensor 0.
extern float gSensor0LatestTouchPos;
extern int gSensor0LatestTouchNum;
uint16_t gPitchLatestInput = 0;

extern float gSensor1LatestTouchPos[];
//extern float gSensor1LatestTouchSizes[];
extern int gSensor1LatestTouchCount;
extern int gSensor1LatestTouchIndex;
int gSensor1LastTouchIndex		= -1;
int gSensor1InputDelayCounter	= -1;
int gSensor1InputIndex			= 0;
float gSensor1MatrixTouchPos[5]	= {0};

// FSR value from matrix input
extern int gLastFSRValue;

// Loop points from matrix input 4
const int gLoopPointsInputBufferSize	= 256;
uint16_t gLoopPointsInputBuffer[gLoopPointsInputBufferSize];
int gLoopPointsInputBufferPointer		= 0;
int gLoopPointMin = 0, gLoopPointMax	= 0;

// multiplier to activate or mute audio in
int audioInStatus = 0;

// xenomai timer
SRTIME prevChangeNs = 0;

// pitch vars
float octaveSplitter;
u_int16_t semitones[((int)N_OCT*12)+1];
float deltaTouch	= 0;
float deltaWeightP	= 0.5;
float deltaWeightI	= 0.0005;

// filter vars
ne10_fir_instance_f32_t filter[2];
ne10_float32_t *filterIn[2];
ne10_float32_t *filterOut[2];
ne10_uint32_t blockSize;
ne10_float32_t *filterState[2];
ne10_float32_t prevFiltered[2];
int filterGain = 80;
ADSR PeakBurst[2];
float peak[2];
float peakThresh = 0.2;

// Tasks for lower-priority calculation
AuxiliaryTask gMediumPriorityRender, gLowPriorityRender;


extern "C" {
	// Function prototype for ARM assembly implementation of oscillator bank
	void oscillator_bank_neon(int numAudioFrames, float *audioOut,
							  int activePartialNum, int lookupTableSize,
							  float *phases, float *frequencies, float *amplitudes,
							  float *freqDerivatives, float *ampDerivatives,
							  float *lookupTable);

	void wavetable_interpolate_neon(int numSamplesIn, int numSamplesOut,
	                              float *tableIn, float *tableOut);
}

void wavetable_interpolate(int numSamplesIn, int numSamplesOut,
                              float *tableIn, float *tableOut,
                              float *sineTable, float sineMix);

inline uint16_t hysteresis_oscillator(uint16_t input, uint16_t risingThreshold,
									uint16_t fallingThreshold, bool *rising);

#ifdef DBOX_CAPE_TEST
void render_capetest(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut,
			uint16_t *matrixIn, uint16_t *matrixOut);
#endif

bool initialise_render(int numMatrixChannels, int numAudioChannels,
					   int numMatrixFramesPerPeriod,
					   int numAudioFramesPerPeriod,
					   float matrixSampleRate, float audioSampleRate,
					   void *userData) {
	int oscBankHopSize = *(int *)userData;

	if(numMatrixChannels != 8) {
		printf("Error: D-Box needs matrix enabled with 8 channels.\n");
		return false;
	}

	// Allocate two buffers for rendering oscillator bank samples
	// One will be used for writing in the background while the other is used for reading
	// on the audio thread. 8-byte alignment needed for the NEON code.
	if(posix_memalign((void **)&gOscillatorBuffer1, 8, oscBankHopSize * gNumAudioChannels * sizeof(float))) {
		printf("Error allocating render buffers\n");
		return false;
	}
	if(posix_memalign((void **)&gOscillatorBuffer2, 8, oscBankHopSize * gNumAudioChannels * sizeof(float))) {
		printf("Error allocating render buffers\n");
		return false;
	}
	gOscillatorBufferWrite	= gOscillatorBuffer1;
	gOscillatorBufferRead	= gOscillatorBuffer2;

	memset(gOscillatorBuffer1, 0, oscBankHopSize * gNumAudioChannels * sizeof(float));
	memset(gOscillatorBuffer2, 0, oscBankHopSize * gNumAudioChannels * sizeof(float));

	// Initialise the dynamic wavetable used by the oscillator bank
	// It should match the size of the static one already allocated in the OscillatorBank object
	// Don't forget a guard point at the end of the table
	gDynamicWavetableLength = gOscBanks[gCurrentOscBank]->lookupTableSize;
	if(posix_memalign((void **)&gDynamicWavetable, 8, (gDynamicWavetableLength + 1) * sizeof(float))) {
		printf("Error allocating wavetable\n");
		return false;
	}

	gFeedbackOscillator.initialise(8192, 10.0, matrixSampleRate);

	for(int n = 0; n < gDynamicWavetableLength + 1; n++)
		gDynamicWavetable[n] = 0;

	// pitch
	float midPos		= (float)65535/2.0;
	octaveSplitter		= round((float)65535/(N_OCT));
	int numOfSemi		= 12*N_OCT;
	int middleSemitone	= 12*N_OCT/2;
	int lastSemitone	= middleSemitone+numOfSemi/2;
	float inc 			= (float)65535/(N_OCT*12.0);
	int i 				= -1;
	for(int semi=middleSemitone; semi<=lastSemitone; semi++)
		semitones[semi] = ( midPos + (++i)*inc) + 0.5;
	i 					= 0;
	for(int semi=middleSemitone-1; semi>=0; semi--)
		semitones[semi] = ( midPos - (++i)*inc) + 0.5;

	if(gAudioIn)
		audioInStatus = 1;

	// filter
	blockSize		= 2*gPeriodSize;
	filterState[0]	= (ne10_float32_t *) NE10_MALLOC ((FILTER_TAP_NUM+blockSize-1) * sizeof (ne10_float32_t));
	filterState[1]	= (ne10_float32_t *) NE10_MALLOC ((FILTER_TAP_NUM+blockSize-1) * sizeof (ne10_float32_t));
	filterIn[0]		= (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t));
	filterIn[1]		= (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t));
	filterOut[0]	= (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t));
	filterOut[1]	= (ne10_float32_t *) NE10_MALLOC (blockSize * sizeof (ne10_float32_t));
	ne10_fir_init_float(&filter[0], FILTER_TAP_NUM, filterTaps, filterState[0], blockSize);
	ne10_fir_init_float(&filter[1], FILTER_TAP_NUM, filterTaps, filterState[1], blockSize);

	// peak outputs
	PeakBurst[0].setAttackRate(.00001 * matrixSampleRate);
	PeakBurst[1].setAttackRate(.00001 * matrixSampleRate);
	PeakBurst[0].setDecayRate(.5 * matrixSampleRate);
	PeakBurst[1].setDecayRate(.5 * matrixSampleRate);
	PeakBurst[0].setSustainLevel(0.0);
	PeakBurst[1].setSustainLevel(0.0);

	// Initialise auxiliary tasks
	if((gMediumPriorityRender = createAuxiliaryTaskLoop(&render_medium_prio, 90, "dbox-calculation-medium")) == 0)
		return false;
	if((gLowPriorityRender = createAuxiliaryTaskLoop(&render_low_prio, 85, "dbox-calculation-low")) == 0)
		return false;

	return true;
}

void render(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut,
			uint16_t *matrixIn, uint16_t *matrixOut)
{
#ifdef DBOX_CAPE_TEST
	render_capetest(numMatrixFrames, numAudioFrames, audioIn, audioOut, matrixIn, matrixOut);
#else
	if(gOscBanks[gCurrentOscBank]->state==bank_toreset)
		gOscBanks[gCurrentOscBank]->resetOscillators();

	if(gOscBanks[gCurrentOscBank]->state==bank_playing)
	{
		assert(gNumAudioChannels == 2);

#ifdef OLD_OSCBANK
		memset(audioOut, 0, numAudioFrames * gNumAudioChannels * sizeof(float));

		/* Render the oscillator bank. The oscillator bank function is written in NEON assembly
		 * and it strips out all extra checks, so find out in advance whether we can render a whole
		 * block or whether the frame will increment in the middle of this buffer.
		 */

		int framesRemaining = numAudioFrames;
		float *audioOutWithOffset = audioOut;

		while(framesRemaining > 0) {
			if(gOscBanks[gCurrentOscBank]->hopCounter >= framesRemaining) {
				/* More frames left in this hop than we need this time. Render and finish */
				oscillator_bank_neon(framesRemaining, audioOutWithOffset,
									 gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize,
									 gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies,
									 gOscBanks[gCurrentOscBank]->oscillatorAmplitudes,
									 gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives,
									 gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives,
									 gDynamicWavetable/*gOscBanks[gCurrentOscBank]->lookupTable*/);
				gOscBanks[gCurrentOscBank]->hopCounter -= framesRemaining;
				if(gOscBanks[gCurrentOscBank]->hopCounter <= 0)
					gOscBanks[gCurrentOscBank]->nextHop();
				framesRemaining = 0;
			}
			else {
				/* More frames to render than are left in this hop. Render and decrement the
				 * number of remaining frames; then advance to the next oscillator frame.
				 */
				oscillator_bank_neon(gOscBanks[gCurrentOscBank]->hopCounter, audioOutWithOffset,
									 gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize,
									 gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies,
									 gOscBanks[gCurrentOscBank]->oscillatorAmplitudes,
									 gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives,
									 gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives,
									 gDynamicWavetable/*gOscBanks[gCurrentOscBank]->lookupTable*/);
				framesRemaining -= gOscBanks[gCurrentOscBank]->hopCounter;
				audioOutWithOffset += gNumAudioChannels * gOscBanks[gCurrentOscBank]->hopCounter;
				gOscBanks[gCurrentOscBank]->sampleCount += gOscBanks[gCurrentOscBank]->hopCounter;
				gOscBanks[gCurrentOscBank]->nextHop();
			}
		}
#else
		for(int n = 0; n < numAudioFrames; n++) {
			audioOut[2*n] 	  = gOscillatorBufferRead[gOscillatorBufferReadPointer++]+audioIn[2*n]*audioInStatus;
			audioOut[2*n + 1] = gOscillatorBufferRead[gOscillatorBufferReadPointer++]+audioIn[2*n+1]*audioInStatus;

			filterIn[0][n] = fabs(audioIn[2*n]);	// rectify for peak detection in 1
			filterIn[1][n] = fabs(audioIn[2*n+1]);	// rectify for peak detection in 2

			/* FIXME why doesn't this work? */
			/*
			if(gOscillatorBufferReadPointer == gOscillatorBufferCurrentSize/2) {
				gOscillatorNeedsRender = true;
				scheduleAuxiliaryTask(gLowPriorityRender);
			} */

			if(gOscillatorBufferReadPointer >= gOscillatorBufferReadCurrentSize) {
				// Finished reading from the buffer: swap to the next buffer
				if(gOscillatorBufferRead == gOscillatorBuffer1) {
					gOscillatorBufferRead = gOscillatorBuffer2;
					gOscillatorBufferWrite = gOscillatorBuffer1;
				}
				else {
					gOscillatorBufferRead = gOscillatorBuffer1;
					gOscillatorBufferWrite = gOscillatorBuffer2;
				}

				// New buffer size is whatever finished writing last hop
				gOscillatorBufferReadCurrentSize = gOscillatorBufferWriteCurrentSize;
				gOscillatorBufferReadPointer = 0;

				gOscillatorNeedsRender = true;
				scheduleAuxiliaryTask(gMediumPriorityRender);
			}
		}
#endif
	}
	else
	{
		for(int n = 0; n < numAudioFrames; n++) {
			audioOut[2*n] 	  = audioIn[2*n]*audioInStatus;
			audioOut[2*n + 1] = audioIn[2*n+1]*audioInStatus;

			filterIn[0][n] = fabs(audioIn[2*n]);	// rectify for peak detection in 1
			filterIn[1][n] = fabs(audioIn[2*n+1]);	// rectify for peak detection in 2
		}
	}

	// low pass filter audio in 1 and 2 for peak detection
	ne10_fir_float_neon(&filter[0], filterIn[0], filterOut[0], blockSize);
	ne10_fir_float_neon(&filter[1], filterIn[1], filterOut[1], blockSize);

	for(int n = 0; n < numMatrixFrames; n++) {


		/* Matrix Out 0, In 0
		 *
		 * CV loop
		 * Controls pitch of sound
		 */
		int touchPosInt = gSensor0LatestTouchPos * 65536.0;
		if(touchPosInt < 0) touchPosInt = 0;
		if(touchPosInt > 65535) touchPosInt = 65535;
		matrixOut[n*8 + DAC_PIN0] = touchPosInt;

		gPitchLatestInput = matrixIn[n*8 + ADC_PIN0];


		/* Matrix Out 7
		 *
		 * Loop feedback with Matrix In 0
		 * Controls discreet pitch
		 */
		float deltaTarget = 0;
		int semitoneIndex = 0;
		if(gSensor0LatestTouchNum>0)
		{
			// current pitch is gPitchLatestInput, already retrieved
			semitoneIndex	= (  ( (float)gPitchLatestInput / 65535)*12*N_OCT  )+0.5;	// closest semitone
			deltaTarget		= (semitones[semitoneIndex]-gPitchLatestInput);				// delta between pitch and target
			deltaTouch 		+= deltaTarget*deltaWeightI;								// update feedback [previous + current]
		}
		else
			deltaTouch = 0;

		int nextOut = touchPosInt  + deltaTarget*deltaWeightP + deltaTouch;			// add feedback to touch -> next out
		if(nextOut < 0) nextOut = 0;												// clamp
		if(nextOut > 65535) nextOut = 65535;										// clamp
		matrixOut[n*8 + DAC_PIN7] = nextOut;										// send next nextOut


		/*
		 * Matrix Out 1, In 1
		 *
		 * Hysteresis (comparator) oscillator
		 * Controls speed of playback
		 */
		bool wasRising = gSpeedHysteresisOscillatorRising;
		matrixOut[n*8 + DAC_PIN1] = hysteresis_oscillator(matrixIn[n*8 + ADC_PIN1], 48000, 16000, &gSpeedHysteresisOscillatorRising);

		// Find interval of zero crossing
		if(wasRising && !gSpeedHysteresisOscillatorRising) {
			int interval = gMatrixSampleCount - gSpeedHysteresisLastTrigger;

			// Interval since last trigger will be the new hop size; calculate to set speed
			if(interval < 1)
				interval = 1;
			//float speed = (float)gOscBanks[gCurrentOscBank]->getHopSize() / (float)interval;
			float speed = 144.0 / interval;	// Normalise to a fixed expected speed
			gOscBanks[gCurrentOscBank]->setSpeed(speed);

			gSpeedHysteresisLastTrigger = gMatrixSampleCount;
		}

		/*
		 * Matrix Out 2, In 2
		 *
		 * Feedback (phase shift) oscillator
		 * Controls wavetable used for oscillator bank
		 */

		int tableLength = gFeedbackOscillator.process(matrixIn[n*8 + ADC_PIN2], &matrixOut[n*8 + DAC_PIN2]);
		if(tableLength != 0) {
			gFeedbackOscillatorTableLength = tableLength;
			gFeedbackOscillatorTable = gFeedbackOscillator.wavetable();
			gDynamicWavetableNeedsRender = true;
			scheduleAuxiliaryTask(gLowPriorityRender);
		}

		/*
		 * Matrix Out 3, In 3
		 *
		 * CV loop with delay for time alignment
		 * Touch positions from sensor 1
		 * Change every 32 samples (ca. 1.5 ms)
		 */
		volatile int touchCount = gSensor1LatestTouchCount;
		if(touchCount == 0)
			matrixOut[n*8 + DAC_PIN3] = 0;
		else {
			int touchIndex = (gMatrixSampleCount >> 5) % touchCount;
			matrixOut[n*8 + DAC_PIN3] = gSensor1LatestTouchPos[touchIndex] * 56000.0f;
			if(touchIndex != gSensor1LastTouchIndex) {
				// Just changed to a new touch output. Reset the counter.
				// It will take 2*matrixFrames samples for this output to come back to the
				// ADC input. But we also want to read near the end of the 32 sample block;
				// let's say 24 samples into it.

				// FIXME this won't work for p > 2
				gSensor1InputDelayCounter = 24 + 2*numMatrixFrames;
				gSensor1InputIndex = touchIndex;
			}
			gSensor1LastTouchIndex = touchIndex;
		}

		if(gSensor1InputDelayCounter-- >= 0 && touchCount > 0) {
			gSensor1MatrixTouchPos[gSensor1InputIndex] = (float)matrixIn[n*8 + ADC_PIN3] / 65536.0f;
		}

		/* Matrix Out 4
		 *
		 * Sensor 1 last pos
		 */
		touchPosInt = gSensor1LatestTouchPos[gSensor1LatestTouchIndex] * 65536.0;
		if(touchPosInt < 0) touchPosInt = 0;
		if(touchPosInt > 65535) touchPosInt = 65535;
		matrixOut[n*8 + DAC_PIN4] = touchPosInt;

		/* Matrix In 4
		 *
		 * Loop points selector
		 */
		gLoopPointsInputBuffer[gLoopPointsInputBufferPointer++] = matrixIn[n*8 + ADC_PIN4];
		if(gLoopPointsInputBufferPointer >= gLoopPointsInputBufferSize) {
			// Find min and max values
			uint16_t loopMax = 0, loopMin = 65535;
			for(int i = 0; i < gLoopPointsInputBufferSize; i++) {
				if(gLoopPointsInputBuffer[i] < loopMin)
					loopMin = gLoopPointsInputBuffer[i];
				if(gLoopPointsInputBuffer[i] > loopMax/* && gLoopPointsInputBuffer[i] != 65535*/)
					loopMax = gLoopPointsInputBuffer[i];
			}

			if(loopMin >= loopMax)
				loopMax = loopMin;

			gLoopPointMax = loopMax;
			gLoopPointMin = loopMin;
			gLoopPointsInputBufferPointer = 0;
		}

		/* Matrix Out 5
		 *
		 * Audio In 1 peak detection and peak burst output
		 */

		filterOut[0][n*2+1]	*= filterGain;
		float burstOut		= PeakBurst[0].getOutput();
		if( burstOut < 0.1)
		{
			if( (prevFiltered[0]>=peakThresh) && (prevFiltered[0]>=filterOut[0][n*2+1]) )
			{
				peak[0] = prevFiltered[0];
				PeakBurst[0].gate(1);
			}
		}

		PeakBurst[0].process(1);

		int convAudio = burstOut*peak[0]*65535;
		matrixOut[n*8 + DAC_PIN5] = convAudio;
		prevFiltered[0] = filterOut[0][n*2+1];
		if(prevFiltered[0]>1)
			prevFiltered[0] = 1;

		/* Matrix In 5
		 *
		 * Dissonance, via changing frequency motion of partials
		 */
		float amount = (float)matrixIn[n*8 + ADC_PIN5] / 65536.0f;
		gOscBanks[gCurrentOscBank]->freqMovement = 1-amount;




		/* Matrix Out 6
		 *
		 * Audio In 2 peak detection and peak burst output
		 */

		filterOut[1][n*2+1]	*= filterGain;
		burstOut			= PeakBurst[1].getOutput();
		if( burstOut < 0.1)
		{
			if( (prevFiltered[1]>=peakThresh) && (prevFiltered[1]>=filterOut[1][n*2+1]) )
			{
				peak[1] = prevFiltered[1];
				PeakBurst[1].gate(1);
			}
		}

		PeakBurst[1].process(1);

		convAudio = burstOut*peak[1]*65535;
		matrixOut[n*8 + DAC_PIN6] = convAudio;
		prevFiltered[1] = filterOut[1][n*2+1];
		if(prevFiltered[1]>1)
			prevFiltered[1] = 1;

		/* Matrix In 6
		 *
		 * Sound selector
		 */
		if(!gIsLoading) {
			// Use hysteresis to avoid jumping back and forth between sounds
			if(gOscBanks.size() > 1) {
				int input = matrixIn[n*8 + ADC_PIN6];
				const int hystValue = 16000;

				int upHysteresisValue = ((gCurrentOscBank + 1) * 65536 + hystValue) / gOscBanks.size();
				int downHysteresisValue = (gCurrentOscBank * 65536 - hystValue) / gOscBanks.size();

				if(input > upHysteresisValue || input < downHysteresisValue) {
					gNextOscBank = input * gOscBanks.size() / 65536;
					if(gNextOscBank < 0)
						gNextOscBank = 0;
					if((unsigned)gNextOscBank >= gOscBanks.size())
						gNextOscBank = gOscBanks.size() - 1;
				}
			}
		}

		/*
		 * Matrix In 7
		 *
		 * FSR from primary touch sensor
		 * Value ranges from 0-1799
		 */
		gLastFSRValue = matrixIn[n*8 + ADC_PIN7] * (1799.0 / 65535.0);
		//gLastFSRValue = 1799 - matrixIn[n*8 + ADC_PIN7] * (1799.0 / 65535.0);
		//dbox_printf("%i\n",gLastFSRValue);

		gMatrixSampleCount++;
	}

#endif /* DBOX_CAPE_TEST */
}

// Medium-priority render function used for audio hop calculations
void render_medium_prio()
{

	if(gOscillatorNeedsRender) {
		gOscillatorNeedsRender = false;

		/* Render one frame into the write buffer */
		memset(gOscillatorBufferWrite, 0, gOscBanks[gCurrentOscBank]->hopCounter * gNumAudioChannels * sizeof(float));

		oscillator_bank_neon(gOscBanks[gCurrentOscBank]->hopCounter, gOscillatorBufferWrite,
							 gOscBanks[gCurrentOscBank]->actPartNum, gOscBanks[gCurrentOscBank]->lookupTableSize,
							 gOscBanks[gCurrentOscBank]->oscillatorPhases, gOscBanks[gCurrentOscBank]->oscillatorNormFrequencies,
							 gOscBanks[gCurrentOscBank]->oscillatorAmplitudes,
							 gOscBanks[gCurrentOscBank]->oscillatorNormFreqDerivatives,
							 gOscBanks[gCurrentOscBank]->oscillatorAmplitudeDerivatives,
							 /*gOscBanks[gCurrentOscBank]->lookupTable*/gDynamicWavetable);

		gOscillatorBufferWriteCurrentSize = gOscBanks[gCurrentOscBank]->hopCounter * gNumAudioChannels;

		/* Update the pitch right before the hop
		 * Total CV range +/- N_OCT octaves
		 */
		float pitch = (float)gPitchLatestInput / octaveSplitter - N_OCT/2;
		//gOscBanks[gCurrentOscBank]->pitchMultiplier = powf(2.0f, pitch);
		gOscBanks[gCurrentOscBank]->pitchMultiplier = pow(2.0f, pitch);

#ifdef FIXME_LATER // This doesn't work very well yet
		gOscBanks[gCurrentOscBank]->filterNum = gSensor1LatestTouchCount;
		float freqScaler = gOscBanks[gCurrentOscBank]->getFrequencyScaler();
		for(int i=0; i < gOscBanks[gCurrentOscBank]->filterNum; i++)
		{
			// touch pos is linear but freqs are log
			gOscBanks[gCurrentOscBank]->filterFreqs[i] = ((expf(gSensor1MatrixTouchPos[i]*4)-1)/(expf(4)-1))*gOscBanks[gCurrentOscBank]->filterMaxF*freqScaler;
			gOscBanks[gCurrentOscBank]->filterQ[i] = gSensor1LatestTouchSizes[i];
			if(gOscBanks[gCurrentOscBank]->filterFreqs[i]>500*freqScaler)
				gOscBanks[gCurrentOscBank]->filterPadding[i] = 1+100000*( (gOscBanks[gCurrentOscBank]->filterFreqs[i]-500*freqScaler)/(gOscBanks[gCurrentOscBank]->filterMaxF-500)*freqScaler );
			else
				gOscBanks[gCurrentOscBank]->filterPadding[i] = 1;
		}
#endif

		RTIME ticks		= rt_timer_read();
		SRTIME ns		= rt_timer_tsc2ns(ticks);
		SRTIME delta 	= ns-prevChangeNs;

		// switch to next bank cannot be too frequent, to avoid seg fault! [for example sef fault happens when removing both VDD and GND from breadboard]
		if(gNextOscBank != gCurrentOscBank && delta>100000000) {

			/*printf("ticks %llu\n", (unsigned long long)ticks);
			printf("ns %llu\n", (unsigned long long)ns);
			printf("prevChangeNs %llu\n", (unsigned long long)prevChangeNs);
			printf("-------------------------->%llud\n", (unsigned long long)(ns-prevChangeNs));*/

			prevChangeNs = ns;
			dbox_printf("Changing to bank %d...\n", gNextOscBank);
			if(gOscBanks[gCurrentOscBank]->state==bank_playing){
				gOscBanks[gCurrentOscBank]->stop();
			}

			gCurrentOscBank = gNextOscBank;
			gOscBanks[gCurrentOscBank]->hopNumTh = 0;
		}
		else {
			/* Advance to the next oscillator frame */
			gOscBanks[gCurrentOscBank]->nextHop();
		}
	}
}

// Lower-priority render function which performs matrix calculations
// State should be transferred in via global variables
void render_low_prio()
{
	gPRU->setGPIOTestPin();
	if(gDynamicWavetableNeedsRender) {
		// Find amplitude of wavetable
		float meanAmplitude = 0;
		float sineMix;

		for(int i = 0; i < gFeedbackOscillatorTableLength; i++) {
			//meanAmplitude += fabsf(gFeedbackOscillatorTable[i]);
			meanAmplitude += fabs(gFeedbackOscillatorTable[i]);
		}
		meanAmplitude /= (float)gFeedbackOscillatorTableLength;

		if(meanAmplitude > 0.35)
			sineMix = 0;
		else
			sineMix = (.35 - meanAmplitude) / .35;

		//dbox_printf("amp %f mix %f\n", meanAmplitude, sineMix);

		// Copy to main wavetable
		wavetable_interpolate(gFeedbackOscillatorTableLength, gDynamicWavetableLength,
				gFeedbackOscillatorTable, gDynamicWavetable,
				gOscBanks[gCurrentOscBank]->lookupTable, sineMix);
	}

	if(gLoopPointMin >= 60000 && gLoopPointMax >= 60000) {
		// KLUDGE!
		if(gCurrentOscBank == 0)
			gOscBanks[gCurrentOscBank]->setLoopHops(50, ((float)gOscBanks[gCurrentOscBank]->getLastHop() * 0.6) - 1);
		else
			gOscBanks[gCurrentOscBank]->setLoopHops(5, ((float)gOscBanks[gCurrentOscBank]->getLastHop() * 0.7) - 1);
	}
	else {
		float normLoopPointMin = (float)gLoopPointMin * gOscBanks[gCurrentOscBank]->getLastHop() / 65535.0;
		float normLoopPointMax = (float)gLoopPointMax * gOscBanks[gCurrentOscBank]->getLastHop() / 65535.0;

		int intLoopPointMin = normLoopPointMin;
		if(intLoopPointMin < 1)
			intLoopPointMin = 1;
		int intLoopPointMax = normLoopPointMax;
		if(intLoopPointMax <= intLoopPointMin)
			intLoopPointMax = intLoopPointMin + 1;
		if(intLoopPointMax > gOscBanks[gCurrentOscBank]->getLastHop() - 1)
			intLoopPointMax =  gOscBanks[gCurrentOscBank]->getLastHop() - 1;

		//dbox_printf("Loop points %d-%d / %d-%d\n", gLoopPointMin, gLoopPointMax, intLoopPointMin, intLoopPointMax);

		/* WORKS, jsut need to fix the glitch when jumps!
		 * *int currentHop = gOscBanks[gCurrentOscBank]->getCurrentHop();
		if(currentHop < intLoopPointMin -1 )
			gOscBanks[gCurrentOscBank]->setJumpHop(intLoopPointMin + 1);
		else if(currentHop > intLoopPointMax + 1)
			gOscBanks[gCurrentOscBank]->setJumpHop(intLoopPointMax - 1);*/
		gOscBanks[gCurrentOscBank]->setLoopHops(intLoopPointMin, intLoopPointMax);
	}

	if(gIsLoading)
		gStatusLED.blink(25, 75);	// Blink quickly until load finished
	else
		gStatusLED.blink(250 / gOscBanks[gCurrentOscBank]->getSpeed(), 250 / gOscBanks[gCurrentOscBank]->getSpeed());
	gPRU->clearGPIOTestPin();

//	static int counter = 32;
//	if(--counter == 0) {
//		for(int i = 0; i < gLoopPointsInputBufferSize; i++) {
//			dbox_printf("%d ", gLoopPointsInputBuffer[i]);
//			if(i % 32 == 31)
//				dbox_printf("\n");
//		}
//		dbox_printf("\n\n");
//		counter = 32;
//	}

	//dbox_printf("min %d max %d\n", gLoopPointMin, gLoopPointMax);
}

// Clean up at the end of render
void cleanup_render()
{
	free(gOscillatorBuffer1);
	free(gOscillatorBuffer2);
	free(gDynamicWavetable);
}

// Interpolate one wavetable into another. The output size
// does not include the guard point at the end which will be identical
// to the first point
void wavetable_interpolate(int numSamplesIn, int numSamplesOut,
                           float *tableIn, float *tableOut,
                           float *sineTable, float sineMix)
{
	float fractionalScaler = (float)numSamplesIn / (float)numSamplesOut;

	for(int k = 0; k < numSamplesOut; k++) {
		float fractionalIndex = (float) k * fractionalScaler;
		//int sB = (int)floorf(fractionalIndex);
		int sB = (int)floor(fractionalIndex);
		int sA = sB + 1;
		if(sA >= numSamplesIn)
			sA = 0;
		float fraction = fractionalIndex - sB;
		tableOut[k] = fraction * tableIn[sA] + (1.0f - fraction) * tableIn[sB];
		tableOut[k] = sineMix * sineTable[k] + (1.0 - sineMix) * tableOut[k];
	}

	tableOut[numSamplesOut] = tableOut[0];
}

// Create a hysteresis oscillator with a matrix input and output
inline uint16_t hysteresis_oscillator(uint16_t input, uint16_t risingThreshold, uint16_t fallingThreshold, bool *rising)
{
	uint16_t value;

	if(*rising) {
		if(input > risingThreshold) {
			*rising = false;
			value = 0;
		}
		else
			value = 65535;
	}
	else {
		if(input < fallingThreshold) {
			*rising = true;
			value = 65535;
		}
		else
			value = 0;
	}

	return value;
}

#ifdef DBOX_CAPE_TEST
// Test the functionality of the D-Box cape by checking each input and output
// Loopback cable from ADC to DAC needed
void render_capetest(int numMatrixFrames, int numAudioFrames, float *audioIn, float *audioOut,
			uint16_t *matrixIn, uint16_t *matrixOut)
{
	static float phase = 0.0;
	static int sampleCounter = 0;
	static int invertChannel = 0;

	// Play a sine wave on the audio output
	for(int n = 0; n < numAudioFrames; n++) {
		audioOut[2*n] = audioOut[2*n + 1] = 0.5*sinf(phase);
		phase += 2.0 * M_PI * 440.0 / 44100.0;
		if(phase >= 2.0 * M_PI)
			phase -= 2.0 * M_PI;
	}

	for(int n = 0; n < numMatrixFrames; n++) {
		// Change outputs every 512 samples
		if(sampleCounter < 512) {
			for(int k = 0; k < 8; k++) {
				if(k == invertChannel)
					matrixOut[n*8 + k] = 50000;
				else
					matrixOut[n*8 + k] = 0;
			}
		}
		else {
			for(int k = 0; k < 8; k++) {
				if(k == invertChannel)
					matrixOut[n*8 + k] = 0;
				else
					matrixOut[n*8 + k] = 50000;
			}
		}

		// Read after 256 samples: input should be low
		if(sampleCounter == 256) {
			for(int k = 0; k < 8; k++) {
				if(k == invertChannel) {
					if(matrixIn[n*8 + k] < 50000) {
						dbox_printf("FAIL channel %d -- output HIGH input %d (inverted)\n", k, matrixIn[n*8 + k]);
					}
				}
				else {
					if(matrixIn[n*8 + k] > 2048) {
						dbox_printf("FAIL channel %d -- output LOW input %d\n", k, matrixIn[n*8 + k]);
					}
				}
			}
		}
		else if(sampleCounter == 768) {
			for(int k = 0; k < 8; k++) {
				if(k == invertChannel) {
					if(matrixIn[n*8 + k] > 2048) {
						dbox_printf("FAIL channel %d -- output LOW input %d (inverted)\n", k, matrixIn[n*8 + k]);
					}
				}
				else {
					if(matrixIn[n*8 + k] < 50000) {
						dbox_printf("FAIL channel %d -- output HIGH input %d\n", k, matrixIn[n*8 + k]);
					}
				}
			}
		}

		if(++sampleCounter >= 1024) {
			sampleCounter = 0;
			invertChannel++;
			if(invertChannel >= 8)
				invertChannel = 0;
		}
	}
}
#endif