andrewm@55: /*
andrewm@55:  * render.cpp
andrewm@55:  *
andrewm@55:  *  Created on: Oct 24, 2014
andrewm@55:  *      Author: parallels
andrewm@55:  */
andrewm@55: 
andrewm@55: 
giuliomoro@301: #include <Bela.h>
andrewm@55: #include <cmath>
andrewm@55: 
andrewm@55: #define ANALOG_LOW	(2048.0 / 65536.0)
andrewm@55: #define ANALOG_HIGH (50000.0 / 65536.0)
andrewm@55: 
andrewm@55: const int gDACPinOrder[] = {6, 4, 2, 0, 1, 3, 5, 7};
andrewm@55: 
andrewm@268: enum {
andrewm@268: 	kStateTestingAudioLeft = 0,
andrewm@268: 	kStateTestingAudioRight,
andrewm@268: 	kStateTestingAudioDone
andrewm@268: };
andrewm@268: 
andrewm@55: uint64_t gLastErrorFrame = 0;
andrewm@55: uint32_t gEnvelopeSampleCount = 0;
andrewm@268: float gEnvelopeValueL = 0.5, gEnvelopeValueR = 0.5;
andrewm@55: float gEnvelopeDecayRate = 0.9995;
andrewm@268: int gEnvelopeLastChannel = 0;
andrewm@268: 
andrewm@268: float gPositivePeakLevels[2] = {0, 0};
andrewm@268: float gNegativePeakLevels[2] = {0, 0};
andrewm@268: float gPeakLevelDecayRate = 0.999;
andrewm@268: const float gPeakLevelLowThreshold = 0.02;
andrewm@268: const float gPeakLevelHighThreshold = 0.2;
andrewm@268: const float gDCOffsetThreshold = 0.1;
andrewm@268: int gAudioTestState = kStateTestingAudioLeft;
andrewm@268: int gAudioTestStateSampleCount = 0;
andrewm@268: int gAudioTestSuccessCounter = 0;
andrewm@268: const int gAudioTestSuccessCounterThreshold = 64;
andrewm@268: const int gAudioTestStateSampleThreshold = 16384;
andrewm@55: 
andrewm@56: // setup() is called once before the audio rendering starts.
andrewm@55: // Use it to perform any initialisation and allocation which is dependent
andrewm@55: // on the period size or sample rate.
andrewm@55: //
andrewm@55: // userData holds an opaque pointer to a data structure that was passed
andrewm@55: // in from the call to initAudio().
andrewm@55: //
andrewm@55: // Return true on success; returning false halts the program.
andrewm@55: 
giuliomoro@301: bool setup(BelaContext *context, void *userData)
andrewm@55: {
andrewm@55: 	return true;
andrewm@55: }
andrewm@55: 
andrewm@55: // render() is called regularly at the highest priority by the audio engine.
andrewm@55: // Input and output are given from the audio hardware and the other
andrewm@55: // ADCs and DACs (if available). If only audio is available, numMatrixFrames
andrewm@55: // will be 0.
andrewm@55: 
giuliomoro@301: void render(BelaContext *context, void *userData)
andrewm@55: {
andrewm@55: 	static float phase = 0.0;
andrewm@55: 	static int sampleCounter = 0;
andrewm@55: 	static int invertChannel = 0;
andrewm@55: 	float frequency = 0;
andrewm@55: 
andrewm@55: 	// Play a sine wave on the audio output
andrewm@55: 	for(unsigned int n = 0; n < context->audioFrames; n++) {
andrewm@268: 		
andrewm@268: 		// Peak detection on the audio inputs, with offset to catch
andrewm@268: 		// DC errors
andrewm@268: 		for(int ch = 0; ch < 2; ch++) {
andrewm@268: 			if(context->audioIn[2*n + ch] > gPositivePeakLevels[ch])
andrewm@268: 				gPositivePeakLevels[ch] = context->audioIn[2*n + ch];
andrewm@268: 			gPositivePeakLevels[ch] += 0.1;
andrewm@268: 			gPositivePeakLevels[ch] *= gPeakLevelDecayRate;
andrewm@268: 			gPositivePeakLevels[ch] -= 0.1;
andrewm@268: 			if(context->audioIn[2*n + ch] < gNegativePeakLevels[ch])
andrewm@268: 				gNegativePeakLevels[ch] = context->audioIn[2*n + ch];
andrewm@268: 			gNegativePeakLevels[ch] -= 0.1;			
andrewm@268: 			gNegativePeakLevels[ch] *= gPeakLevelDecayRate;
andrewm@268: 			gNegativePeakLevels[ch] += 0.1;
andrewm@268: 		}
andrewm@268: 		
andrewm@268: 		if(gAudioTestState == kStateTestingAudioLeft) {
andrewm@268: 			context->audioOut[2*n] = 0.2 * sinf(phase);
andrewm@268: 			context->audioOut[2*n + 1] = 0;		
andrewm@268: 			
andrewm@268: 			frequency = 3000.0;
andrewm@268: 			phase += 2.0 * M_PI * frequency / 44100.0;
andrewm@268: 			if(phase >= 2.0 * M_PI)
andrewm@268: 				phase -= 2.0 * M_PI;
andrewm@268: 			
andrewm@268: 			gAudioTestStateSampleCount++;
andrewm@268: 			if(gAudioTestStateSampleCount >= gAudioTestStateSampleThreshold) {
andrewm@268: 				// Check if we have the expected input: signal on the left but not
andrewm@268: 				// on the right. Also check that there is not too much DC offset on the
andrewm@268: 				// inactive signal
andrewm@268: 				if((gPositivePeakLevels[0] - gNegativePeakLevels[0]) >= gPeakLevelHighThreshold 
andrewm@268: 					&& (gPositivePeakLevels[1] -  gNegativePeakLevels[1]) <= gPeakLevelLowThreshold &&
andrewm@268: 					fabsf(gPositivePeakLevels[1]) < gDCOffsetThreshold &&
andrewm@268: 					fabsf(gNegativePeakLevels[1]) < gDCOffsetThreshold) {
andrewm@268: 					// Successful test: increment counter
andrewm@268: 					gAudioTestSuccessCounter++;
andrewm@268: 					if(gAudioTestSuccessCounter >= gAudioTestSuccessCounterThreshold) {
andrewm@268: 						gAudioTestState = kStateTestingAudioRight;
andrewm@268: 						gAudioTestStateSampleCount = 0;
andrewm@268: 						gAudioTestSuccessCounter = 0;
andrewm@268: 					}
andrewm@55: 
andrewm@268: 				}
andrewm@268: 				else {
andrewm@311: 					if(!((context->audioFramesElapsed + n) % 22050)) {
andrewm@268: 						// Debugging print messages
andrewm@268: 						if((gPositivePeakLevels[0] - gNegativePeakLevels[0]) < gPeakLevelHighThreshold)
andrewm@268: 							rt_printf("Left Audio In FAIL: insufficient signal: %f\n", 
andrewm@268: 										gPositivePeakLevels[0] - gNegativePeakLevels[0]);
andrewm@268: 						else if(gPositivePeakLevels[1] -  gNegativePeakLevels[1] > gPeakLevelLowThreshold)
andrewm@268: 							rt_printf("Right Audio In FAIL: signal present when it should not be: %f\n",
andrewm@268: 										gPositivePeakLevels[1] -  gNegativePeakLevels[1]);
andrewm@268: 						else if(fabsf(gPositivePeakLevels[1]) >= gDCOffsetThreshold ||
andrewm@268: 								fabsf(gNegativePeakLevels[1]) >= gDCOffsetThreshold)
andrewm@268: 							rt_printf("Right Audio In FAIL: DC offset: (%f, %f)\n",
andrewm@268: 										gPositivePeakLevels[1], gNegativePeakLevels[1]);						
andrewm@268: 					}
andrewm@268: 					gAudioTestSuccessCounter--;
andrewm@268: 					if(gAudioTestSuccessCounter <= 0)
andrewm@268: 						gAudioTestSuccessCounter = 0;
andrewm@268: 				}
andrewm@55: 			}
andrewm@268: 		}
andrewm@268: 		else if(gAudioTestState == kStateTestingAudioRight) {
andrewm@268: 			context->audioOut[2*n] = 0;
andrewm@268: 			context->audioOut[2*n + 1] = 0.2 * sinf(phase);
andrewm@268: 			
andrewm@268: 			frequency = 3000.0;
andrewm@268: 			phase += 2.0 * M_PI * frequency / 44100.0;
andrewm@268: 			if(phase >= 2.0 * M_PI)
andrewm@268: 				phase -= 2.0 * M_PI;
andrewm@268: 			
andrewm@268: 			gAudioTestStateSampleCount++;
andrewm@268: 			if(gAudioTestStateSampleCount >= gAudioTestStateSampleThreshold) {
andrewm@268: 				// Check if we have the expected input: signal on the left but not
andrewm@268: 				// on the right
andrewm@268: 				if((gPositivePeakLevels[1] - gNegativePeakLevels[1]) >= gPeakLevelHighThreshold 
andrewm@268: 					&& (gPositivePeakLevels[0] -  gNegativePeakLevels[0]) <= gPeakLevelLowThreshold &&
andrewm@268: 					fabsf(gPositivePeakLevels[0]) < gDCOffsetThreshold &&
andrewm@268: 					fabsf(gNegativePeakLevels[0]) < gDCOffsetThreshold) {
andrewm@268: 					// Successful test: increment counter
andrewm@268: 					gAudioTestSuccessCounter++;
andrewm@268: 					if(gAudioTestSuccessCounter >= gAudioTestSuccessCounterThreshold) {
andrewm@268: 						gAudioTestSuccessCounter = 0;							
andrewm@268: 						gAudioTestStateSampleCount = 0;
andrewm@268: 						gAudioTestState = kStateTestingAudioDone;
andrewm@268: 					}
andrewm@268: 				}
andrewm@268: 				else {
andrewm@311: 					if(!((context->audioFramesElapsed + n) % 22050)) {
andrewm@268: 						// Debugging print messages
andrewm@268: 						if((gPositivePeakLevels[1] - gNegativePeakLevels[1]) < gPeakLevelHighThreshold)
andrewm@268: 							rt_printf("Right Audio In FAIL: insufficient signal: %f\n", 
andrewm@268: 										gPositivePeakLevels[1] - gNegativePeakLevels[1]);
andrewm@268: 						else if(gPositivePeakLevels[0] -  gNegativePeakLevels[0] > gPeakLevelLowThreshold)
andrewm@268: 							rt_printf("Left Audio In FAIL: signal present when it should not be: %f\n",
andrewm@268: 										gPositivePeakLevels[0] -  gNegativePeakLevels[0]);
andrewm@268: 						else if(fabsf(gPositivePeakLevels[0]) >= gDCOffsetThreshold ||
andrewm@268: 								fabsf(gNegativePeakLevels[0]) >= gDCOffsetThreshold)
andrewm@268: 							rt_printf("Left Audio In FAIL: DC offset: (%f, %f)\n",
andrewm@268: 										gPositivePeakLevels[0], gNegativePeakLevels[0]);						
andrewm@268: 					}
andrewm@268: 					gAudioTestSuccessCounter--;
andrewm@268: 					if(gAudioTestSuccessCounter <= 0)
andrewm@268: 						gAudioTestSuccessCounter = 0;
andrewm@268: 				}
andrewm@268: 			}			
andrewm@55: 		}
andrewm@55: 		else {
andrewm@268: 			// Audio input testing finished. Play tones depending on status of
andrewm@268: 			// analog testing
andrewm@268: 			context->audioOut[2*n] = gEnvelopeValueL * sinf(phase);
andrewm@268: 			context->audioOut[2*n + 1] = gEnvelopeValueR * sinf(phase);
andrewm@268: 
andrewm@268: 			// If one second has gone by with no error, play one sound, else
andrewm@268: 			// play another
andrewm@311: 			if(context->audioFramesElapsed + n - gLastErrorFrame > 44100) {
andrewm@268: 				gEnvelopeValueL *= gEnvelopeDecayRate;
andrewm@268: 				gEnvelopeValueR *= gEnvelopeDecayRate;
andrewm@268: 				gEnvelopeSampleCount++;
andrewm@268: 				if(gEnvelopeSampleCount > 22050) {
andrewm@268: 					if(gEnvelopeLastChannel == 0)
andrewm@268: 						gEnvelopeValueR = 0.5;
andrewm@268: 					else
andrewm@268: 						gEnvelopeValueL = 0.5;
andrewm@268: 					gEnvelopeLastChannel = !gEnvelopeLastChannel;
andrewm@268: 					gEnvelopeSampleCount = 0;
andrewm@268: 				}
andrewm@268: 				frequency = 880.0;
andrewm@268: 			}
andrewm@268: 			else {
andrewm@268: 				gEnvelopeValueL = gEnvelopeValueR = 0.5;
andrewm@268: 				gEnvelopeLastChannel = 0;
andrewm@268: 				frequency = 220.0;
andrewm@268: 			}
andrewm@268: 
andrewm@268: 			phase += 2.0 * M_PI * frequency / 44100.0;
andrewm@268: 			if(phase >= 2.0 * M_PI)
andrewm@268: 				phase -= 2.0 * M_PI;
andrewm@55: 		}
andrewm@55: 	}
andrewm@55: 
andrewm@55: 	for(unsigned int n = 0; n < context->analogFrames; n++) {
andrewm@55: 		// Change outputs every 512 samples
andrewm@55: 		if(sampleCounter < 512) {
andrewm@55: 			for(int k = 0; k < 8; k++) {
andrewm@55: 				if(k == invertChannel)
andrewm@55: 					context->analogOut[n*8 + gDACPinOrder[k]] = ANALOG_HIGH;
andrewm@55: 				else
andrewm@55: 					context->analogOut[n*8 + gDACPinOrder[k]] = 0;
andrewm@55: 			}
andrewm@55: 		}
andrewm@55: 		else {
andrewm@55: 			for(int k = 0; k < 8; k++) {
andrewm@55: 				if(k == invertChannel)
andrewm@55: 					context->analogOut[n*8 + gDACPinOrder[k]] = 0;
andrewm@55: 				else
andrewm@55: 					context->analogOut[n*8 + gDACPinOrder[k]] = ANALOG_HIGH;
andrewm@55: 			}
andrewm@55: 		}
andrewm@55: 
andrewm@55: 		// Read after 256 samples: input should be low
andrewm@55: 		if(sampleCounter == 256) {
andrewm@55: 			for(int k = 0; k < 8; k++) {
andrewm@55: 				if(k == invertChannel) {
andrewm@55: 					if(context->analogIn[n*8 + k] < ANALOG_HIGH) {
andrewm@55: 						rt_printf("FAIL [output %d, input %d] -- output HIGH input %f (inverted)\n", gDACPinOrder[k], k, context->analogIn[n*8 + k]);
andrewm@311: 						gLastErrorFrame = context->audioFramesElapsed + n;
andrewm@55: 					}
andrewm@55: 				}
andrewm@55: 				else {
andrewm@55: 					if(context->analogIn[n*8 + k] > ANALOG_LOW) {
andrewm@55: 						rt_printf("FAIL [output %d, input %d] -- output LOW --> input %f\n", gDACPinOrder[k], k, context->analogIn[n*8 + k]);
andrewm@311: 						gLastErrorFrame = context->audioFramesElapsed + n;
andrewm@55: 					}
andrewm@55: 				}
andrewm@55: 			}
andrewm@55: 		}
andrewm@55: 		else if(sampleCounter == 768) {
andrewm@55: 			for(int k = 0; k < 8; k++) {
andrewm@55: 				if(k == invertChannel) {
andrewm@55: 					if(context->analogIn[n*8 + k] > ANALOG_LOW) {
andrewm@55: 						rt_printf("FAIL [output %d, input %d] -- output LOW input %f (inverted)\n", gDACPinOrder[k], k, context->analogIn[n*8 + k]);
andrewm@311: 						gLastErrorFrame = context->audioFramesElapsed + n;
andrewm@55: 					}
andrewm@55: 				}
andrewm@55: 				else {
andrewm@55: 					if(context->analogIn[n*8 + k] < ANALOG_HIGH) {
andrewm@55: 						rt_printf("FAIL [output %d, input %d] -- output HIGH input %f\n", gDACPinOrder[k], k, context->analogIn[n*8 + k]);
andrewm@311: 						gLastErrorFrame = context->audioFramesElapsed + n;
andrewm@55: 					}
andrewm@55: 				}
andrewm@55: 			}
andrewm@55: 		}
andrewm@55: 
andrewm@55: 		if(++sampleCounter >= 1024) {
andrewm@55: 			sampleCounter = 0;
andrewm@55: 			invertChannel++;
andrewm@55: 			if(invertChannel >= 8)
andrewm@55: 				invertChannel = 0;
andrewm@55: 		}
andrewm@55: 	}
andrewm@55: }
andrewm@55: 
andrewm@56: // cleanup() is called once at the end, after the audio has stopped.
andrewm@56: // Release any resources that were allocated in setup().
andrewm@55: 
giuliomoro@301: void cleanup(BelaContext *context, void *userData)
andrewm@55: {
andrewm@55: 
andrewm@55: }