view examples/10-Instruments/d-box/render.cpp @ 543:8f8809c77dda prerelease

updated basics, digital, instruments, extras examples
author chnrx <chris.heinrichs@gmail.com>
date Fri, 24 Jun 2016 13:19:52 +0100
parents 8fcfbfb32aa0
children
line wrap: on
line source
/*
 * render.cpp
 *
 *  Created on: May 28, 2014
 *      Author: Victor Zappi
 */

#include <Bela.h>
#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

// Mappings from pin numbers on PCB to actual DAC channels
// This gives the DAC and ADC connectors the same effective pinout
// Update June 2016: this is no longer needed in the latest Bela
// release, but is kept here for convenience: it used to be 
// 6 4 2 0 1 3 5 7 for the DAC pins
#define DAC_PIN0	0
#define DAC_PIN1	1
#define DAC_PIN2	2
#define DAC_PIN3	3
#define DAC_PIN4	4
#define DAC_PIN5	5
#define DAC_PIN6	6
#define DAC_PIN7	7

#define ADC_PIN0	0
#define ADC_PIN1	1
#define ADC_PIN2	2
#define ADC_PIN3	3
#define ADC_PIN4	4
#define ADC_PIN5	5
#define ADC_PIN6	6
#define ADC_PIN7	7

#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;

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;
float 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;
float gLoopPointsInputBuffer[gLoopPointsInputBufferSize];
int gLoopPointsInputBufferPointer		= 0;
float gLoopPointMin = 0, gLoopPointMax	= 0;

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

// xenomai timer
SRTIME prevChangeNs = 0;

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

// 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 float hysteresis_oscillator(float input, float risingThreshold,
									float fallingThreshold, bool *rising);

void render_medium_prio();
void render_low_prio();

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

bool setup(BelaContext *context, void *userData) {
	int oscBankHopSize = *(int *)userData;

	if(context->analogOutChannels <= 8 || context->analogInChannels <= 8) {
		printf("Error: D-Box needs at least 8 analog IO channels.\n");
		return false;
	}

	if(context->audioInChannels != context->audioOutChannels ||
			context->analogInChannels != context-> analogOutChannels){
		printf("Error: for this project, you need the same number of input and output 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 * context->audioOutChannels * sizeof(float))) {
		printf("Error allocating render buffers\n");
		return false;
	}
	if(posix_memalign((void **)&gOscillatorBuffer2, 8, oscBankHopSize * context->audioOutChannels * sizeof(float))) {
		printf("Error allocating render buffers\n");
		return false;
	}
	gOscillatorBufferWrite	= gOscillatorBuffer1;
	gOscillatorBufferRead	= gOscillatorBuffer2;

	memset(gOscillatorBuffer1, 0, oscBankHopSize * context->audioOutChannels * sizeof(float));
	memset(gOscillatorBuffer2, 0, oscBankHopSize * context->audioOutChannels * 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, context->analogSampleRate);

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

	// pitch
	float midPos		= 0.5;
	octaveSplitter		= 1.0 / N_OCT;
	int numOfSemi		= 12*N_OCT;
	int middleSemitone	= 12*N_OCT/2;
	int lastSemitone	= middleSemitone+numOfSemi/2;
	float inc 			= 1.0 / (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		= context->audioFrames;
	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 * context->analogSampleRate);
	PeakBurst[1].setAttackRate(.00001 * context->analogSampleRate);
	PeakBurst[0].setDecayRate(.5 * context->analogSampleRate);
	PeakBurst[1].setDecayRate(.5 * context->analogSampleRate);
	PeakBurst[0].setSustainLevel(0.0);
	PeakBurst[1].setSustainLevel(0.0);

	// Initialise auxiliary tasks
	if((gMediumPriorityRender = Bela_createAuxiliaryTask(&render_medium_prio, BELA_AUDIO_PRIORITY - 10, "dbox-calculation-medium")) == 0)
		return false;
	if((gLowPriorityRender = Bela_createAuxiliaryTask(&render_low_prio, BELA_AUDIO_PRIORITY - 15, "dbox-calculation-low")) == 0)
		return false;

	return true;
}

void render(BelaContext *context, void *userData)
{
#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(context->audioOutChannels == 2);

#ifdef OLD_OSCBANK
		memset(audioOut, 0, numAudioFrames *  * 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 +=  * gOscBanks[gCurrentOscBank]->hopCounter;
				gOscBanks[gCurrentOscBank]->sampleCount += gOscBanks[gCurrentOscBank]->hopCounter;
				gOscBanks[gCurrentOscBank]->nextHop();
			}
		}
#else
		for(unsigned int n = 0; n < context->audioFrames; n++) {
			context->audioOut[2*n] 	  = gOscillatorBufferRead[gOscillatorBufferReadPointer++]+context->audioIn[2*n]*audioInStatus;
			context->audioOut[2*n + 1] = gOscillatorBufferRead[gOscillatorBufferReadPointer++]+context->audioIn[2*n+1]*audioInStatus;

			filterIn[0][n] = fabs(context->audioIn[2*n]);	// rectify for peak detection in 1
			filterIn[1][n] = fabs(context->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;
				Bela_scheduleAuxiliaryTask(gMediumPriorityRender);
			}
		}
#endif
	}
	else
	{
		for(unsigned int n = 0; n < context->audioFrames; n++) {
			context->audioOut[2*n] 	  = context->audioIn[2*n]*audioInStatus;
			context->audioOut[2*n + 1] = context->audioIn[2*n+1]*audioInStatus;

			filterIn[0][n] = fabs(context->audioIn[2*n]);	// rectify for peak detection in 1
			filterIn[1][n] = fabs(context->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(unsigned int n = 0; n < context->analogFrames; n++) {


		/* Matrix Out 0, In 0
		 *
		 * CV loop
		 * Controls pitch of sound
		 */
		float touchPosInt = gSensor0LatestTouchPos;
		if(touchPosInt < 0) touchPosInt = 0;
		if(touchPosInt > 1.0) touchPosInt = 1.0;
		context->analogOut[n*8 + DAC_PIN0] = touchPosInt;

		gPitchLatestInput = context->analogIn[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	= ( gPitchLatestInput * 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;

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


		/*
		 * Matrix Out 1, In 1
		 *
		 * Hysteresis (comparator) oscillator
		 * Controls speed of playback
		 */
		bool wasRising = gSpeedHysteresisOscillatorRising;
		context->analogOut[n*8 + DAC_PIN1] = hysteresis_oscillator(context->analogIn[n*8 + ADC_PIN1], 48000.0/65536.0,
																	16000.0/65536.0, &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(context->analogIn[n*8 + ADC_PIN2], &context->analogOut[n*8 + DAC_PIN2]);
		if(tableLength != 0) {
			gFeedbackOscillatorTableLength = tableLength;
			gFeedbackOscillatorTable = gFeedbackOscillator.wavetable();
			gDynamicWavetableNeedsRender = true;
			Bela_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)
			context->analogOut[n*8 + DAC_PIN3] = 0;
		else {
			int touchIndex = (gMatrixSampleCount >> 5) % touchCount;
			context->analogOut[n*8 + DAC_PIN3] = gSensor1LatestTouchPos[touchIndex] * 56000.0f / 65536.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*context->analogFrames;
				gSensor1InputIndex = touchIndex;
			}
			gSensor1LastTouchIndex = touchIndex;
		}

		if(gSensor1InputDelayCounter-- >= 0 && touchCount > 0) {
			gSensor1MatrixTouchPos[gSensor1InputIndex] = context->analogIn[n*8 + ADC_PIN3];
		}

		/* Matrix Out 4
		 *
		 * Sensor 1 last pos
		 */
		touchPosInt = gSensor1LatestTouchPos[gSensor1LatestTouchIndex];
		if(touchPosInt < 0) touchPosInt = 0;
		if(touchPosInt > 1.0) touchPosInt = 1.0;
		context->analogOut[n*8 + DAC_PIN4] = touchPosInt;

		/* Matrix In 4
		 *
		 * Loop points selector
		 */
		gLoopPointsInputBuffer[gLoopPointsInputBufferPointer++] = context->analogIn[n*8 + ADC_PIN4];
		if(gLoopPointsInputBufferPointer >= gLoopPointsInputBufferSize) {
			// Find min and max values
			float loopMax = 0, loopMin = 1.0;
			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);

		float convAudio = burstOut*peak[0];
		context->analogOut[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)context->analogIn[n*8 + ADC_PIN5];
		gOscBanks[gCurrentOscBank]->freqMovement = 1.0 - 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];
		context->analogOut[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) {
				float input = context->analogIn[n*8 + ADC_PIN6];
				const float hystValue = 16000.0 / 65536.0;

				float upHysteresisValue = ((gCurrentOscBank + 1) + hystValue) / gOscBanks.size();
				float downHysteresisValue = (gCurrentOscBank - hystValue) / gOscBanks.size();

				if(input > upHysteresisValue || input < downHysteresisValue) {
					gNextOscBank = input * gOscBanks.size();
					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 = context->analogIn[n*8 + ADC_PIN7] * 1799.0;
		//gLastFSRValue = 1799 - context->analogIn[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 * 2 * sizeof(float)); /* assumes 2 audio channels */

		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 * 2;

		/* 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.0/65536.0 && gLoopPointMax >= 60000.0/65536.0) {
		// 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();
		float normLoopPointMax = (float)gLoopPointMax * gOscBanks[gCurrentOscBank]->getLastHop();

		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(BelaContext *context, void *userData)
{
	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 float hysteresis_oscillator(float input, float risingThreshold, float fallingThreshold, bool *rising)
{
	float value;

	if(*rising) {
		if(input > risingThreshold) {
			*rising = false;
			value = 0;
		}
		else
			value = 1.0;
	}
	else {
		if(input < fallingThreshold) {
			*rising = true;
			value = 1.0;
		}
		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